Add support for managed profile configuration
- Add framework for reading and enforcing managed profile restrictions.
- Implement enforcement of search engine restriction.
- Add automated test for search engine restriction.
(cherry-picked from 4688934c52fa74e0c4280e4105e644f10221a8ed)
Change-Id: Ifb6018df584fedba42e86ab35d9bfa53b4d36ebe
diff --git a/src/com/android/browser/Browser.java b/src/com/android/browser/Browser.java
index bd2ac06..d1c127d 100644
--- a/src/com/android/browser/Browser.java
+++ b/src/com/android/browser/Browser.java
@@ -40,10 +40,18 @@
// Set to true to enable extra debug logging.
final static boolean LOGD_ENABLED = true;
+ private static Context mContext;
+
+ public static Context getContext() {
+ return mContext;
+ }
+
@Override
public void onCreate() {
super.onCreate();
+ mContext = this;
+
if (LOGV_ENABLED) {
Log.v(LOGTAG, "Browser.onCreate: this=" + this);
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 5bb085c..b5db3af 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -32,6 +32,7 @@
import com.android.browser.R;
import com.android.browser.homepages.HomeProvider;
+import com.android.browser.mdm.SearchEngineRestriction;
import com.android.browser.platformsupport.Browser;
import com.android.browser.provider.BrowserProvider;
import com.android.browser.search.SearchEngine;
@@ -701,6 +702,10 @@
// -----------------------------
public String getSearchEngineName() {
+ // The following is a NOP if the SEARCH_ENGINE restriction has already been created. Otherwise,
+ // it creates the restriction and if enabled it sets the <default_search_engine_value>.
+ SearchEngineRestriction.getInstance();
+
String defaultSearchEngineValue = mContext.getString(R.string.default_search_engine_value);
if (defaultSearchEngineValue == null) {
defaultSearchEngineValue = SearchEngine.GOOGLE;
diff --git a/src/com/android/browser/mdm/ManagedProfileManager.java b/src/com/android/browser/mdm/ManagedProfileManager.java
new file mode 100644
index 0000000..210af6d
--- /dev/null
+++ b/src/com/android/browser/mdm/ManagedProfileManager.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.mdm;
+
+import android.annotation.TargetApi;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.browser.Browser;
+
+import org.codeaurora.swe.util.Observable;
+
+/**
+ * Restrictions manager and merger. Layers 3 levels of restrictions:
+ *
+ * 1. Device administrator policies (device administrators are for example the Email app, or
+ * traditional MDMs)
+ *
+ * 2. User restricted profile policies (different users, such as the 'guest' user can have
+ * restrictions on APPs). Note that we don't advertise the restrictions, which is a
+ * possibility allowed by the framework and which can be used by parents to restrict certain
+ * aspects of APPs to children, for example.
+ *
+ * 3. Provisioned properties, for example from the 'Android at Work' project in which
+ * strings with an understood meaning are available for the apps to be read.
+ *
+ * Persistency: The Android framework makes sure the properties will persist even in case of
+ * uninstalling the application (just for 3, 2 and 1 are app independent).
+ *
+ * Availability: Restrictions are available right after creating this class. If a delegate is
+ * passed in the constructor it will be notified of events, such as the change in
+ * provisioned policies (3).
+ *
+ */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class ManagedProfileManager extends Observable {
+
+ private final static String TAG = "ManagedProfileManager";
+
+ // this string is literal because not publicly defined as of API 21
+ private final static String INTENT_ACTION_POLICY_CHANGE = "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED";
+
+ /* Generic platform restriction keys. Booleans default to false, missing from bundle means unrestricted */
+ private static final String VOLUME_CHANGE_DISABLE = "VolumeChangeDisable"; // boolean
+ private static final String VPN_CHANGE_DISABLED = "VpnChangeDisabled"; // boolean
+ private static final String LOCATION_DISABLED = "LocationDisabled"; // boolean
+ private static final String CAMERA_DISABLED = "CameraDisabled"; // boolean
+ private static final String MICROPHONE_DISABLED = "MicrophoneDisabled"; // boolean
+ private static final String SCREEN_CAPTURE_DISABLED = "ScreenCaptureDisabled"; // boolean
+ private static final String WEB_DEVELOPMENT_DISABLED = "WebDevelopmentDisabled"; // boolean
+ private static final String STORAGE_ENCRYPTION_REQUIRED = "StorageEncryptionRequired"; // boolean
+ private static final String PASSWORDS_MINIMUM_LENGTH = "PasswordsMinimumLength"; // integer
+ private static final String PASSWORDS_MINIMUM_LETTERS = "PasswordsMinimumLetters"; // integer
+ private static final String PASSWORDS_MINIMUM_NON_LETTERS = "PasswordsMinimumNonLetters"; // integer
+
+ private static ManagedProfileManager sInstance = null;
+
+ private final Context mContext;
+
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final UserManager mUserPolicyManager;
+
+ // cached policies; the first two won't change during execution, the third may
+ private Bundle mDeviceAdministratorRestrictions;
+ private Bundle mUserRestrictions;
+ private Bundle mMdmProvisioningRestrictions;
+
+ private BroadcastReceiver mMdmBroadcastReceiver;
+
+ public static ManagedProfileManager getInstance() {
+ if (sInstance == null)
+ sInstance = new ManagedProfileManager(Browser.getContext());
+ return sInstance;
+ }
+
+ private ManagedProfileManager(Context context) {
+ mContext = context;
+
+ mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mUserPolicyManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+ // Fetch restrictions
+ getMdmPackageRestrictions(mContext.getPackageName());
+ getDeviceAdministratorRestrictions();
+ getUserRestrictions();
+
+ mergeRestrictions();
+
+ // listen for any change
+ registerMdmPolicyChangeListener();
+ }
+
+ /**
+ * Reads the restrictions which are set by the MDM Administrator
+ * @param packageName
+ */
+ private void getMdmPackageRestrictions(String packageName) {
+ try {
+ // no need to map the MDM restrictions since they're already in the right format
+ // the keys in the bundle have the values of the Restriction.* constants
+ mMdmProvisioningRestrictions =
+ mUserPolicyManager.getApplicationRestrictions(packageName);
+ } catch (SecurityException e) {
+ // Only the system can get/set restrictions on other apps
+ }
+ }
+
+ /**
+ * Reads and maps any Android User Restriction (e.g. 'user can't use the microphone')
+ */
+ private void getUserRestrictions() {
+ Bundle b;
+ try {
+ b = mUserPolicyManager.getUserRestrictions();
+ } catch (Exception e) {
+ return;
+ }
+
+ // map pertinent user restrictions to our restrictions (the list comes from the
+ // UserManager doc, and was last revised for API 21)
+ mUserRestrictions = new Bundle();
+ if (Build.VERSION.SDK_INT >= 18) {
+ if (b.getBoolean(UserManager.DISALLOW_SHARE_LOCATION, false))
+ mUserRestrictions.putBoolean(LOCATION_DISABLED, true);
+ }
+ if (Build.VERSION.SDK_INT >= 21) {
+ if (b.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME, false))
+ mUserRestrictions.putBoolean(VOLUME_CHANGE_DISABLE, true);
+ if (b.getBoolean(UserManager.DISALLOW_CONFIG_VPN, false))
+ mUserRestrictions.putBoolean(VPN_CHANGE_DISABLED, true);
+ if (b.getBoolean(UserManager.DISALLOW_DEBUGGING_FEATURES, false))
+ mUserRestrictions.putBoolean(WEB_DEVELOPMENT_DISABLED, true);
+ if (b.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE, false))
+ mUserRestrictions.putBoolean(MICROPHONE_DISABLED, true);
+ }
+ }
+
+ /**
+ * Reads and maps any Android Device Restriction (e.g. 'this device can't access location'),
+ * which are set by legacy MDMs.
+ */
+ @SuppressWarnings("ConstantConditions")
+ private void getDeviceAdministratorRestrictions() {
+ mDeviceAdministratorRestrictions = new Bundle();
+
+ // We are checking all the administrators, not just targeting one in particular - we'll
+ // get the more restrictive set of values
+ final ComponentName n = null;
+
+ try {
+ // map pertinent administrators restrictions to our restrictions
+ if (mDevicePolicyManager.getCameraDisabled(n))
+ mDeviceAdministratorRestrictions.putBoolean(CAMERA_DISABLED, true);
+ int passwordMinimumLength = mDevicePolicyManager.getPasswordMinimumLength(n);
+ if (passwordMinimumLength > 0)
+ mDeviceAdministratorRestrictions.putInt(PASSWORDS_MINIMUM_LENGTH,
+ passwordMinimumLength);
+ int passwordMinimumLetters = mDevicePolicyManager.getPasswordMinimumLetters(n);
+ // default minimum number of letters required is 1
+ if (passwordMinimumLetters > 1)
+ mDeviceAdministratorRestrictions.putInt(PASSWORDS_MINIMUM_LETTERS,
+ passwordMinimumLetters);
+ int passwordMinimumNonletters = mDevicePolicyManager.getPasswordMinimumNonLetter(n);
+ if (passwordMinimumNonletters > 0)
+ mDeviceAdministratorRestrictions.putInt(PASSWORDS_MINIMUM_NON_LETTERS,
+ passwordMinimumNonletters);
+ // NOTE: there are more passwords requirement which haven't been parsed yet because
+ // the author deemed that superfluous
+ if (mDevicePolicyManager.getStorageEncryption(n))
+ mDeviceAdministratorRestrictions.putBoolean(STORAGE_ENCRYPTION_REQUIRED, true);
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ if (mDevicePolicyManager.getScreenCaptureDisabled(n))
+ mDeviceAdministratorRestrictions.putBoolean(SCREEN_CAPTURE_DISABLED, true);
+ }
+ } catch (Exception e) {
+ // better safe than sorry
+ Log.e(TAG, "Error reading from the policy manager: " + e.getMessage());
+ }
+ }
+
+ public void onActivityDestroy() {
+ unregisterMdmPolicyChangeListener();
+ }
+
+ /* private implementation ahead */
+
+ private void mergeRestrictions() {
+ // Merge restrictions
+ Bundle restrictions = new Bundle();
+ if (mDeviceAdministratorRestrictions != null)
+ restrictions.putAll(mDeviceAdministratorRestrictions);
+ if (mUserRestrictions != null)
+ restrictions.putAll(mUserRestrictions);
+ if (mMdmProvisioningRestrictions != null)
+ restrictions.putAll(mMdmProvisioningRestrictions);
+
+ // notify observers
+ set(restrictions);
+ }
+
+ private void registerMdmPolicyChangeListener() {
+ // create a listener that acts upon receiving data
+ mMdmBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action == INTENT_ACTION_POLICY_CHANGE) {
+ getMdmPackageRestrictions(mContext.getPackageName());
+ mergeRestrictions();
+ }
+ }
+ };
+
+ // listen in broadcast
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_ACTION_POLICY_CHANGE);
+ mContext.registerReceiver(mMdmBroadcastReceiver, filter);
+ }
+
+ private void unregisterMdmPolicyChangeListener() {
+ if (mMdmBroadcastReceiver != null) {
+ mContext.unregisterReceiver(mMdmBroadcastReceiver);
+ mMdmBroadcastReceiver = null;
+ }
+ }
+
+ /**
+ * Added for testing
+ * @param restrictions The set of restrictions to apply.
+ */
+ protected void setMdmRestrictions(Bundle restrictions) {
+ mMdmProvisioningRestrictions = restrictions;
+ mergeRestrictions();
+ }
+}
diff --git a/src/com/android/browser/mdm/Restriction.java b/src/com/android/browser/mdm/Restriction.java
new file mode 100644
index 0000000..5d9fbf9
--- /dev/null
+++ b/src/com/android/browser/mdm/Restriction.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.mdm;
+
+import android.os.Bundle;
+
+import org.codeaurora.swe.util.Activator;
+import org.codeaurora.swe.util.Observable;
+
+/**
+ * Abstract implementation of a restriction set by a Mobile Device Management (MDM) agent on browser
+ * instances running in a managed profile. A subclass must implement the abstract method enforce()
+ * to set the restriction.
+ */
+public abstract class Restriction {
+
+ private static boolean mEnabled = false;
+
+ public Restriction() {
+ // Register observer for restrictions
+ Activator.activate(new Observable.Observer() {
+ @Override
+ public void onChange(Object... params) {
+ if (params[0] != null) {
+ enforce((Bundle) params[0]);
+ }
+ }
+ }, ManagedProfileManager.getInstance());
+ }
+
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ public void enable(boolean enable) {
+ mEnabled = enable;
+ }
+
+ abstract public void enforce(Bundle restrictions);
+}
+
diff --git a/src/com/android/browser/mdm/RestrictionsTest.java b/src/com/android/browser/mdm/RestrictionsTest.java
new file mode 100644
index 0000000..96eb65c
--- /dev/null
+++ b/src/com/android/browser/mdm/RestrictionsTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.mdm;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Bundle;
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserSettings;
+import com.android.browser.PreferenceKeys;
+import com.android.browser.R;
+
+public class RestrictionsTest extends ActivityInstrumentationTestCase2<BrowserActivity>
+ implements PreferenceKeys {
+
+ private final static String TAG = "RestrictionsTest";
+ private final static String VALID_SEARCH_ENGINE_NAME_1 = "netsprint";
+ private final static String VALID_SEARCH_ENGINE_NAME_2 = "naver";
+ //Search engine name that does not match an entry in res/values/all_search_engines.xml
+ private final static String INVALID_SEARCH_ENGINE_NAME = "foo";
+
+ private Instrumentation mInstrumentation;
+ private Context mContext;
+ private BrowserActivity mActivity;
+
+ public RestrictionsTest() {
+ super(BrowserActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mInstrumentation = getInstrumentation();
+ mContext = getInstrumentation().getTargetContext();
+ mActivity = getActivity();
+ }
+
+ public void testSetDefaultSearchProvider() throws Throwable {
+ SearchEngineRestriction searchEngineRestriction = SearchEngineRestriction.getInstance();
+
+ // Ensure we start with the default search engine and no restriction
+ String defaultSearchEngineName = mActivity.getApplicationContext()
+ .getString(R.string.default_search_engine_value);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is not set when DefaultSearchProviderEnabled is not present
+ setDefaultSearchProvider(null, VALID_SEARCH_ENGINE_NAME_1);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is not set when DefaultSearchProviderEnabled is FALSE
+ setDefaultSearchProvider(false, VALID_SEARCH_ENGINE_NAME_1);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is not set when DefaultSearchProviderName is not present
+ setDefaultSearchProvider(true, null);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is not set when DefaultSearchProviderName is INVALID
+ setDefaultSearchProvider(true, INVALID_SEARCH_ENGINE_NAME);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is set when DefaultSearchProviderEnabled is TRUE and
+ // DefaultSearchProviderName is VALID
+ setDefaultSearchProvider(true, VALID_SEARCH_ENGINE_NAME_1);
+ assertTrue("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", VALID_SEARCH_ENGINE_NAME_1,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ setDefaultSearchProvider(true, VALID_SEARCH_ENGINE_NAME_2);
+ assertTrue("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", VALID_SEARCH_ENGINE_NAME_2,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is lifted when neither DefaultSearchProviderEnabled nor
+ // DefaultSearchProviderName are present
+ setDefaultSearchProvider(null, null);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is lifted when DefaultSearchProviderEnabled is FALSE
+
+ // first set a valid search engine restriction
+ setDefaultSearchProvider(true, VALID_SEARCH_ENGINE_NAME_1);
+ assertTrue("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", VALID_SEARCH_ENGINE_NAME_1,
+ BrowserSettings.getInstance().getSearchEngineName());
+ // then lift the restriction
+ setDefaultSearchProvider(false, VALID_SEARCH_ENGINE_NAME_2);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+
+ // Restriction is lifted when DefaultSearchProviderEnabled is TRUE and
+ // DefaultSearchProviderName is INVALID
+
+ // first set a valid search engine restriction
+ setDefaultSearchProvider(true, VALID_SEARCH_ENGINE_NAME_1);
+ assertTrue("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", VALID_SEARCH_ENGINE_NAME_1,
+ BrowserSettings.getInstance().getSearchEngineName());
+ // then lift the restriction
+ setDefaultSearchProvider(true, INVALID_SEARCH_ENGINE_NAME);
+ assertFalse("Search engine restriction", searchEngineRestriction.isEnabled());
+ assertEquals("Search provider", defaultSearchEngineName,
+ BrowserSettings.getInstance().getSearchEngineName());
+ }
+
+ /**
+ * Activate search engine restriction
+ * @param defaultSearchProviderEnabled must be true to activate restriction
+ * @param defaultSearchProviderName must be an entry in res/values/all_search_engines.xml,
+ * otherwise restriction is not set
+ */
+ private void setDefaultSearchProvider(Boolean defaultSearchProviderEnabled,
+ String defaultSearchProviderName) {
+ // Construct restriction bundle
+ final Bundle restrictions = new Bundle();
+ if (defaultSearchProviderEnabled != null)
+ restrictions.putBoolean("DefaultSearchProviderEnabled", defaultSearchProviderEnabled);
+ if (defaultSearchProviderName != null)
+ restrictions.putString("SearchProviderName", defaultSearchProviderName);
+
+ // Deliver restriction on UI thread
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ManagedProfileManager.getInstance().setMdmRestrictions(restrictions);
+ }
+ });
+
+ // Wait to ensure restriction is set
+ mInstrumentation.waitForIdleSync();
+ }
+
+}
diff --git a/src/com/android/browser/mdm/SearchEngineRestriction.java b/src/com/android/browser/mdm/SearchEngineRestriction.java
new file mode 100644
index 0000000..23a75a0
--- /dev/null
+++ b/src/com/android/browser/mdm/SearchEngineRestriction.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.mdm;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+
+import com.android.browser.Browser;
+import com.android.browser.BrowserSettings;
+import com.android.browser.PreferenceKeys;
+import com.android.browser.R;
+import com.android.browser.search.SearchEngineInfo;
+import com.android.browser.search.SearchEngines;
+
+public class SearchEngineRestriction extends Restriction implements PreferenceKeys {
+
+ private final static String TAG = "SearchEngineRestriction";
+
+ private static final String DEFAULT_SEARCH_PROVIDER_ENABLED = "DefaultSearchProviderEnabled"; // boolean
+ private static final String SEARCH_PROVIDER_NAME = "SearchProviderName"; // String
+
+ private static SearchEngineRestriction sInstance;
+
+ private SearchEngineInfo mSearchEngineInfo;
+
+ private SearchEngineRestriction() {
+ super();
+ }
+
+ public static SearchEngineRestriction getInstance() {
+ synchronized (SearchEngineRestriction.class) {
+ if (sInstance == null) {
+ sInstance = new SearchEngineRestriction();
+ }
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void enforce(Bundle restrictions) {
+ SharedPreferences.Editor editor = BrowserSettings.getInstance().getPreferences().edit();
+ String searchEngineName = restrictions.getString(SEARCH_PROVIDER_NAME);
+ SearchEngineInfo searchEngineInfo = null;
+ Context context = Browser.getContext();
+
+ if (searchEngineName != null)
+ searchEngineInfo = SearchEngines.getSearchEngineInfo(context, searchEngineName);
+
+ if (restrictions.getBoolean(DEFAULT_SEARCH_PROVIDER_ENABLED, false) &&
+ searchEngineInfo != null) {
+ mSearchEngineInfo = searchEngineInfo;
+ // Set search engine
+ editor.putString(PREF_SEARCH_ENGINE, searchEngineName);
+ editor.apply();
+ enable(true);
+ } else if (isEnabled()) {
+ mSearchEngineInfo = null;
+ enable(false);
+ // Restore default search engine
+ editor.putString(PREF_SEARCH_ENGINE,
+ context.getString(R.string.default_search_engine_value));
+ editor.apply();
+ }
+ }
+
+ public SearchEngineInfo getSearchEngineInfo() {
+ return mSearchEngineInfo;
+ }
+}
diff --git a/src/com/android/browser/preferences/GeneralPreferencesFragment.java b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
index e7850de..c2c72b2 100644
--- a/src/com/android/browser/preferences/GeneralPreferencesFragment.java
+++ b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
@@ -48,11 +48,12 @@
import com.android.browser.R;
import com.android.browser.UrlUtils;
import com.android.browser.homepages.HomeProvider;
+import com.android.browser.mdm.SearchEngineRestriction;
public class GeneralPreferencesFragment extends PreferenceFragment
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
- static final String TAG = "PersonalPreferencesFragment";
+ static final String TAG = "GeneralPreferencesFragment";
public static final String EXTRA_CURRENT_PAGE = "currentPage";
@@ -92,6 +93,11 @@
PreferenceKeys.PREF_AUTOFILL_PROFILE);
autofill.setOnPreferenceClickListener(this);
+ //Disable set search engine preference if SEARCH_ENGINE restriction is enabled
+ if (SearchEngineRestriction.getInstance().isEnabled()) {
+ findPreference("search_engine").setEnabled(false);
+ }
+
mAdvFrag = new AdvancedPreferencesFragment(this);
mPrivFrag = new PrivacySecurityPreferencesFragment(this);
}
diff --git a/src/com/android/browser/search/SearchEnginePreference.java b/src/com/android/browser/search/SearchEnginePreference.java
index 020fd23..a129ef1 100644
--- a/src/com/android/browser/search/SearchEnginePreference.java
+++ b/src/com/android/browser/search/SearchEnginePreference.java
@@ -16,6 +16,7 @@
package com.android.browser.search;
import com.android.browser.R;
+import com.android.browser.mdm.SearchEngineRestriction;
import android.app.SearchManager;
import android.content.ComponentName;
@@ -26,6 +27,8 @@
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
import java.util.ArrayList;
@@ -46,16 +49,41 @@
entryValues.add(defaultSearchEngineName);
entries.add(defaultSearchEngine.getLabel());
}
- for (SearchEngineInfo searchEngineInfo : SearchEngines.getSearchEngineInfos(context)) {
- String name = searchEngineInfo.getName();
- // Skip entry with same name as default provider
- if (!name.equals(defaultSearchEngineName)) {
- entryValues.add(name);
- entries.add(searchEngineInfo.getLabel());
+
+ SearchEngineInfo managedSearchEngineInfo = SearchEngineRestriction.getInstance()
+ .getSearchEngineInfo();
+
+ if (managedSearchEngineInfo != null) {
+ // Add managed searched engine to the list if SEARCH_ENGINE restriction is enabled
+ entryValues.add(managedSearchEngineInfo.getName());
+ entries.add(managedSearchEngineInfo.getLabel());
+ } else {
+ for (SearchEngineInfo searchEngineInfo : SearchEngines.getSearchEngineInfos(context)) {
+ String name = searchEngineInfo.getName();
+ // Skip entry if name is same as the default or the managed
+ if (!name.equals(defaultSearchEngineName)) {
+ entryValues.add(name);
+ entries.add(searchEngineInfo.getLabel());
+ }
}
}
setEntryValues(entryValues.toArray(new CharSequence[entryValues.size()]));
setEntries(entries.toArray(new CharSequence[entries.size()]));
}
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ if (!isEnabled()) {
+ view.setEnabled(true);
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Toast.makeText(getContext(), R.string.mdm_managed_alert, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
}