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();
+                }
+            });
+        }
+    }
 }