android: add serverURL configuration for MDM/EMM environments

Android for Enterprise provides special feature for applications to obtain configuration through RestrictionManager remotely by some MDM solution.

Jitsi Meet can be remotely installed and provisioned with a proper URL (making URL not editable by the user) inside the Work Profile or Fully managed device.
This commit is contained in:
Roman 2020-06-26 12:47:48 +03:00 committed by GitHub
parent 3b1ad9faff
commit 4b1743bb2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 9 deletions

View File

@ -7,6 +7,9 @@
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<meta-data
android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:label="@string/app_name"

View File

@ -16,9 +16,15 @@
package org.jitsi.meet;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.RestrictionEntry;
import android.content.RestrictionsManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
@ -31,6 +37,7 @@ import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
/**
@ -48,6 +55,27 @@ public class MainActivity extends JitsiMeetActivity {
private static final int OVERLAY_PERMISSION_REQUEST_CODE
= (int) (Math.random() * Short.MAX_VALUE);
/**
* ServerURL configuration key for restriction configuration using {@link android.content.RestrictionsManager}
*/
public static final String RESTRICTION_SERVER_URL = "SERVER_URL";
/**
* Broadcast receiver for restrictions handling
*/
private BroadcastReceiver broadcastReceiver;
/**
* Flag if configuration is provided by RestrictionManager
*/
private boolean configurationByRestrictions = false;
/**
* Default URL as could be obtained from RestrictionManager
*/
private String defaultURL;
// JitsiMeetActivity overrides
//
@ -85,16 +113,66 @@ public class MainActivity extends JitsiMeetActivity {
@Override
protected void initialize() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// As new restrictions including server URL are received,
// conference should be restarted with new configuration.
leave();
recreate();
}
};
registerReceiver(broadcastReceiver,
new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED));
resolveRestrictions();
setJitsiMeetConferenceDefaultOptions();
super.initialize();
}
@Override
public void onDestroy() {
if (broadcastReceiver != null) {
unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
super.onDestroy();
}
private void setJitsiMeetConferenceDefaultOptions() {
// Set default options
JitsiMeetConferenceOptions defaultOptions
= new JitsiMeetConferenceOptions.Builder()
.setWelcomePageEnabled(true)
.setServerURL(buildURL("https://meet.jit.si"))
.setFeatureFlag("call-integration.enabled", false)
.build();
.setWelcomePageEnabled(true)
.setServerURL(buildURL(defaultURL))
.setFeatureFlag("call-integration.enabled", false)
.setFeatureFlag("server-url-change.enabled", !configurationByRestrictions)
.build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
}
super.initialize();
private void resolveRestrictions() {
RestrictionsManager manager =
(RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
Bundle restrictions = manager.getApplicationRestrictions();
Collection<RestrictionEntry> entries = manager.getManifestRestrictions(
getApplicationContext().getPackageName());
for (RestrictionEntry restrictionEntry : entries) {
String key = restrictionEntry.getKey();
if (RESTRICTION_SERVER_URL.equals(key)) {
// If restrictions are passed to the application.
if (restrictions != null &&
restrictions.containsKey(RESTRICTION_SERVER_URL)) {
defaultURL = restrictions.getString(RESTRICTION_SERVER_URL);
configurationByRestrictions = true;
// Otherwise use default URL from app-restrictions.xml.
} else {
defaultURL = restrictionEntry.getSelectedString();
configurationByRestrictions = false;
}
}
}
}
@Override

View File

@ -1,3 +1,5 @@
<resources>
<string name="app_name">Jitsi Meet</string>
<string name="restriction_server_url_description">URL of Jitsi Meet server instance to connect to</string>
<string name="restriction_server_url_title">Server URL</string>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Server URL configuration -->
<restriction
android:defaultValue="https://meet.jit.si"
android:description="@string/restriction_server_url_description"
android:key="SERVER_URL"
android:restrictionType="string"
android:title="@string/restriction_server_url_title"/>
</restrictions>

View File

@ -4,7 +4,7 @@ import React from 'react';
import { setColorScheme } from '../../base/color-scheme';
import { DialogContainer } from '../../base/dialog';
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED, updateFlags } from '../../base/flags';
import { Platform } from '../../base/react';
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
@ -86,6 +86,20 @@ export class App extends AbstractApp {
// We set these early enough so then we avoid any unnecessary re-renders.
const { dispatch } = this.state.store;
// Check if serverURL is configured externally and not allowed to change.
const serverURLChangeEnabled = this.props.flags[SERVER_URL_CHANGE_ENABLED];
if (!serverURLChangeEnabled) {
// As serverURL is provided externally, so we push it to settings.
if (typeof this.props.url !== 'undefined') {
const { serverURL } = this.props.url;
if (typeof serverURL !== 'undefined') {
dispatch(updateSettings({ serverURL }));
}
}
}
dispatch(setColorScheme(this.props.colorScheme));
dispatch(updateFlags(this.props.flags));
dispatch(updateSettings(this.props.userInfo || {}));

View File

@ -81,6 +81,12 @@ export const RAISE_HAND_ENABLED = 'raise-hand.enabled';
*/
export const RECORDING_ENABLED = 'recording.enabled';
/**
* Flag indicating if server URL change is enabled.
* Default: enabled (true)
*/
export const SERVER_URL_CHANGE_ENABLED = 'server-url-change.enabled';
/**
* Flag indicating if tile view feature should be enabled.
* Default: enabled.

View File

@ -7,11 +7,11 @@ import { translate } from '../../../base/i18n';
import { JitsiModal } from '../../../base/modal';
import { connect } from '../../../base/redux';
import { SETTINGS_VIEW_ID } from '../../constants';
import { normalizeUserInputURL } from '../../functions';
import { normalizeUserInputURL, isServerURLChangeEnabled } from '../../functions';
import {
AbstractSettingsView,
_mapStateToProps as _abstractMapStateToProps,
type Props
type Props as AbstractProps
} from '../AbstractSettingsView';
import FormRow from './FormRow';
@ -70,6 +70,20 @@ type State = {
startWithVideoMuted: boolean,
}
/**
* The type of the React {@code Component} props of
* {@link SettingsView}.
*/
type Props = AbstractProps & {
/**
* Flag indicating if URL can be changed by user.
*
* @protected
*/
_serverURLChangeEnabled: boolean
}
/**
* The native container rendering the app settings page.
*
@ -168,6 +182,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
<TextInput
autoCapitalize = 'none'
autoCorrect = { false }
editable = { this.props._serverURLChangeEnabled }
onBlur = { this._onBlurServerURL }
onChangeText = { this._onChangeServerURL }
placeholder = { this.props._serverURL }
@ -514,7 +529,8 @@ class SettingsView extends AbstractSettingsView<Props, State> {
*/
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state)
..._abstractMapStateToProps(state),
_serverURLChangeEnabled: isServerURLChangeEnabled(state)
};
}

View File

@ -1,4 +1,6 @@
// @flow
import { SERVER_URL_CHANGE_ENABLED, getFeatureFlag } from '../base/flags';
import { i18next, DEFAULT_LANGUAGE, LANGUAGES } from '../base/i18n';
import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
import {
@ -23,6 +25,20 @@ export function isSettingEnabled(settingName: string) {
return interfaceConfig.SETTINGS_SECTIONS.includes(settingName);
}
/**
* Returns true if user is allowed to change Server URL.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state.
* @returns {boolean} True to indicate that user can change Server URL, false otherwise.
*/
export function isServerURLChangeEnabled(stateful: Object | Function) {
const state = toState(stateful);
const flag = getFeatureFlag(state, SERVER_URL_CHANGE_ENABLED, true);
return flag;
}
/**
* Normalizes a URL entered by the user.
* FIXME: Consider adding this to base/util/uri.