Restrictions Manager

Mechanism to register a provider for requesting an
administrator to respond to permission requests.

Request format and response format constants.

Description of manifest template for static restrictions.
Int type introduced in RestrictionEntry.

Needs more javadoc and better description of manifest templates,
including specifying the XML attributes.

Change-Id: I5a654d364e98379fc60f73db2e06bf9a8310263d
diff --git a/Android.mk b/Android.mk
index c58ef53..2f3a990 100644
--- a/Android.mk
+++ b/Android.mk
@@ -118,6 +118,7 @@
 	core/java/android/content/IIntentReceiver.aidl \
 	core/java/android/content/IIntentSender.aidl \
 	core/java/android/content/IOnPrimaryClipChangedListener.aidl \
+	core/java/android/content/IRestrictionsManager.aidl \
 	core/java/android/content/ISyncAdapter.aidl \
 	core/java/android/content/ISyncContext.aidl \
 	core/java/android/content/ISyncServiceAdapter.aidl \
diff --git a/api/current.txt b/api/current.txt
index 0c3702e..856037f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5229,6 +5229,7 @@
     method public void setPasswordMinimumUpperCase(android.content.ComponentName, int);
     method public void setPasswordQuality(android.content.ComponentName, int);
     method public void setProfileEnabled(android.content.ComponentName);
+    method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public int setStorageEncryption(android.content.ComponentName, boolean);
     method public void wipeData(int);
@@ -7024,6 +7025,7 @@
     field public static final java.lang.String NSD_SERVICE = "servicediscovery";
     field public static final java.lang.String POWER_SERVICE = "power";
     field public static final java.lang.String PRINT_SERVICE = "print";
+    field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
     field public static final java.lang.String SEARCH_SERVICE = "search";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
@@ -7780,12 +7782,14 @@
     ctor public RestrictionEntry(java.lang.String, java.lang.String);
     ctor public RestrictionEntry(java.lang.String, boolean);
     ctor public RestrictionEntry(java.lang.String, java.lang.String[]);
+    ctor public RestrictionEntry(java.lang.String, int);
     ctor public RestrictionEntry(android.os.Parcel);
     method public int describeContents();
     method public java.lang.String[] getAllSelectedStrings();
     method public java.lang.String[] getChoiceEntries();
     method public java.lang.String[] getChoiceValues();
     method public java.lang.String getDescription();
+    method public int getIntValue();
     method public java.lang.String getKey();
     method public boolean getSelectedState();
     method public java.lang.String getSelectedString();
@@ -7797,6 +7801,7 @@
     method public void setChoiceValues(java.lang.String[]);
     method public void setChoiceValues(android.content.Context, int);
     method public void setDescription(java.lang.String);
+    method public void setIntValue(int);
     method public void setSelectedState(boolean);
     method public void setSelectedString(java.lang.String);
     method public void setTitle(java.lang.String);
@@ -7805,10 +7810,36 @@
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int TYPE_BOOLEAN = 1; // 0x1
     field public static final int TYPE_CHOICE = 2; // 0x2
+    field public static final int TYPE_INTEGER = 5; // 0x5
     field public static final int TYPE_MULTI_SELECT = 4; // 0x4
     field public static final int TYPE_NULL = 0; // 0x0
   }
 
+  public class RestrictionsManager {
+    method public android.os.Bundle getApplicationRestrictions();
+    method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
+    method public boolean hasRestrictionsProvider();
+    method public void notifyPermissionResponse(java.lang.String, android.os.Bundle);
+    method public void requestPermission(java.lang.String, android.os.Bundle);
+    field public static final java.lang.String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+    field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.intent.action.REQUEST_PERMISSION";
+    field public static final java.lang.String EXTRA_PACKAGE_NAME = "package_name";
+    field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "request_bundle";
+    field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+    field public static final java.lang.String EXTRA_TEMPLATE_ID = "template_id";
+    field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+    field public static final java.lang.String REQUEST_KEY_DATA = "android.req_template.data";
+    field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+    field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+    field public static final java.lang.String REQUEST_KEY_ICON = "android.req_template.icon";
+    field public static final java.lang.String REQUEST_KEY_ID = "android.req_template.req_id";
+    field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+    field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+    field public static final java.lang.String REQUEST_KEY_TITLE = "android.req_template.title";
+    field public static final java.lang.String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+    field public static final java.lang.String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+  }
+
   public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
     ctor public SearchRecentSuggestionsProvider();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 460732f..4f335bb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -35,7 +35,9 @@
 import android.content.IntentFilter;
 import android.content.IIntentReceiver;
 import android.content.IntentSender;
+import android.content.IRestrictionsManager;
 import android.content.ReceiverCallNotAllowedException;
+import android.content.RestrictionsManager;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
@@ -662,6 +664,13 @@
             }
         });
 
+        registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE);
+                IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
+                return new RestrictionsManager(ctx, service);
+            }
+        });
         registerService(PRINT_SERVICE, new ServiceFetcher() {
             public Object createService(ContextImpl ctx) {
                 IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6b486c4..b3b1d47 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -25,6 +25,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.RestrictionsManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Process;
@@ -2320,4 +2321,23 @@
         }
     }
 
+    /**
+     * Designates a specific broadcast receiver component as the provider for
+     * making permission requests of a local or remote administrator of the user.
+     * <p/>
+     * Only a profile owner can designate the restrictions provider.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param receiver The component name of a BroadcastReceiver that handles the
+     * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null,
+     * it removes the restrictions provider previously assigned.
+     */
+    public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) {
+        if (mService != null) {
+            try {
+                mService.setRestrictionsProvider(admin, receiver);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to set permission provider on device policy service");
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index cc6d715..7f754dc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -121,6 +121,9 @@
     void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
     Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
 
+    void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
+    ComponentName getRestrictionsProvider(int userHandle);
+
     void setUserRestriction(in ComponentName who, in String key, boolean enable);
     void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
     void clearCrossProfileIntentFilters(in ComponentName admin);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d3a979c..c69e669 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2695,6 +2695,15 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.content.RestrictionsManager} for retrieving application restrictions
+     * and requesting permissions for restricted operations.
+     * @see #getSystemService
+     * @see android.content.RestrictionsManager
+     */
+    public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.app.AppOpsManager} for tracking application operations
      * on the device.
      *
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
new file mode 100644
index 0000000..b1c0a3a
--- /dev/null
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Bundle;
+
+/**
+ * Interface used by the RestrictionsManager
+ * @hide
+ */
+interface IRestrictionsManager {
+    Bundle getApplicationRestrictions(in String packageName);
+    boolean hasRestrictionsProvider();
+    void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
+    void notifyPermissionResponse(in String packageName, in Bundle response);
+}
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 3ff53bf..62f88a9 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -73,32 +73,38 @@
      */
     public static final int TYPE_MULTI_SELECT = 4;
 
+    /**
+     * A type of restriction. Use this for storing an integer value. The range of values
+     * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
+     */
+    public static final int TYPE_INTEGER = 5;
+
     /** The type of restriction. */
-    private int type;
+    private int mType;
 
     /** The unique key that identifies the restriction. */
-    private String key;
+    private String mKey;
 
     /** The user-visible title of the restriction. */
-    private String title;
+    private String mTitle;
 
     /** The user-visible secondary description of the restriction. */
-    private String description;
+    private String mDescription;
 
     /** The user-visible set of choices used for single-select and multi-select lists. */
-    private String [] choices;
+    private String [] mChoiceEntries;
 
     /** The values corresponding to the user-visible choices. The value(s) of this entry will
      * one or more of these, returned by {@link #getAllSelectedStrings()} and
      * {@link #getSelectedString()}.
      */
-    private String [] values;
+    private String [] mChoiceValues;
 
     /* The chosen value, whose content depends on the type of the restriction. */
-    private String currentValue;
+    private String mCurrentValue;
 
     /* List of selected choices in the multi-select case. */
-    private String[] currentValues;
+    private String[] mCurrentValues;
 
     /**
      * Constructor for {@link #TYPE_CHOICE} type.
@@ -106,9 +112,9 @@
      * @param selectedString the current value
      */
     public RestrictionEntry(String key, String selectedString) {
-        this.key = key;
-        this.type = TYPE_CHOICE;
-        this.currentValue = selectedString;
+        this.mKey = key;
+        this.mType = TYPE_CHOICE;
+        this.mCurrentValue = selectedString;
     }
 
     /**
@@ -117,8 +123,8 @@
      * @param selectedState whether this restriction is selected or not
      */
     public RestrictionEntry(String key, boolean selectedState) {
-        this.key = key;
-        this.type = TYPE_BOOLEAN;
+        this.mKey = key;
+        this.mType = TYPE_BOOLEAN;
         setSelectedState(selectedState);
     }
 
@@ -128,9 +134,20 @@
      * @param selectedStrings the list of values that are currently selected
      */
     public RestrictionEntry(String key, String[] selectedStrings) {
-        this.key = key;
-        this.type = TYPE_MULTI_SELECT;
-        this.currentValues = selectedStrings;
+        this.mKey = key;
+        this.mType = TYPE_MULTI_SELECT;
+        this.mCurrentValues = selectedStrings;
+    }
+
+    /**
+     * Constructor for {@link #TYPE_INTEGER} type.
+     * @param key the unique key for this restriction
+     * @param selectedInt the integer value of the restriction
+     */
+    public RestrictionEntry(String key, int selectedInt) {
+        mKey = key;
+        mType = TYPE_INTEGER;
+        setIntValue(selectedInt);
     }
 
     /**
@@ -138,7 +155,7 @@
      * @param type the type for this restriction.
      */
     public void setType(int type) {
-        this.type = type;
+        this.mType = type;
     }
 
     /**
@@ -146,7 +163,7 @@
      * @return the type for this restriction
      */
     public int getType() {
-        return type;
+        return mType;
     }
 
     /**
@@ -155,7 +172,7 @@
      * single string values.
      */
     public String getSelectedString() {
-        return currentValue;
+        return mCurrentValue;
     }
 
     /**
@@ -164,7 +181,7 @@
      *  null otherwise.
      */
     public String[] getAllSelectedStrings() {
-        return currentValues;
+        return mCurrentValues;
     }
 
     /**
@@ -172,7 +189,23 @@
      * @return the current selected state of the entry.
      */
     public boolean getSelectedState() {
-        return Boolean.parseBoolean(currentValue);
+        return Boolean.parseBoolean(mCurrentValue);
+    }
+
+    /**
+     * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}.
+     * @return the integer value of the entry.
+     */
+    public int getIntValue() {
+        return Integer.parseInt(mCurrentValue);
+    }
+
+    /**
+     * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}.
+     * @param value the integer value to set.
+     */
+    public void setIntValue(int value) {
+        mCurrentValue = Integer.toString(value);
     }
 
     /**
@@ -181,7 +214,7 @@
      * @param selectedString the string value to select.
      */
     public void setSelectedString(String selectedString) {
-        currentValue = selectedString;
+        mCurrentValue = selectedString;
     }
 
     /**
@@ -190,7 +223,7 @@
      * @param state the current selected state
      */
     public void setSelectedState(boolean state) {
-        currentValue = Boolean.toString(state);
+        mCurrentValue = Boolean.toString(state);
     }
 
     /**
@@ -199,7 +232,7 @@
      * @param allSelectedStrings the current list of selected values.
      */
     public void setAllSelectedStrings(String[] allSelectedStrings) {
-        currentValues = allSelectedStrings;
+        mCurrentValues = allSelectedStrings;
     }
 
     /**
@@ -216,7 +249,7 @@
      * @see #getAllSelectedStrings()
      */
     public void setChoiceValues(String[] choiceValues) {
-        values = choiceValues;
+        mChoiceValues = choiceValues;
     }
 
     /**
@@ -227,7 +260,7 @@
      * @see #setChoiceValues(String[])
      */
     public void setChoiceValues(Context context, int stringArrayResId) {
-        values = context.getResources().getStringArray(stringArrayResId);
+        mChoiceValues = context.getResources().getStringArray(stringArrayResId);
     }
 
     /**
@@ -235,7 +268,7 @@
      * @return the list of possible values.
      */
     public String[] getChoiceValues() {
-        return values;
+        return mChoiceValues;
     }
 
     /**
@@ -248,7 +281,7 @@
      * @see #setChoiceValues(String[])
      */
     public void setChoiceEntries(String[] choiceEntries) {
-        choices = choiceEntries;
+        mChoiceEntries = choiceEntries;
     }
 
     /** Sets a list of strings that will be presented as choices to the user. This is similar to
@@ -257,7 +290,7 @@
      * @param stringArrayResId the resource id of a string array containing the possible entries.
      */
     public void setChoiceEntries(Context context, int stringArrayResId) {
-        choices = context.getResources().getStringArray(stringArrayResId);
+        mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
     }
 
     /**
@@ -265,7 +298,7 @@
      * @return the list of choices presented to the user.
      */
     public String[] getChoiceEntries() {
-        return choices;
+        return mChoiceEntries;
     }
 
     /**
@@ -273,7 +306,7 @@
      * @return the user-visible description, null if none was set earlier.
      */
     public String getDescription() {
-        return description;
+        return mDescription;
     }
 
     /**
@@ -283,7 +316,7 @@
      * @param description the user-visible description string.
      */
     public void setDescription(String description) {
-        this.description = description;
+        this.mDescription = description;
     }
 
     /**
@@ -291,7 +324,7 @@
      * @return the key for the restriction.
      */
     public String getKey() {
-        return key;
+        return mKey;
     }
 
     /**
@@ -299,7 +332,7 @@
      * @return the user-visible title for the entry, null if none was set earlier.
      */
     public String getTitle() {
-        return title;
+        return mTitle;
     }
 
     /**
@@ -307,7 +340,7 @@
      * @param title the user-visible title for the entry.
      */
     public void setTitle(String title) {
-        this.title = title;
+        this.mTitle = title;
     }
 
     private boolean equalArrays(String[] one, String[] other) {
@@ -324,23 +357,23 @@
         if (!(o instanceof RestrictionEntry)) return false;
         final RestrictionEntry other = (RestrictionEntry) o;
         // Make sure that either currentValue matches or currentValues matches.
-        return type == other.type && key.equals(other.key)
+        return mType == other.mType && mKey.equals(other.mKey)
                 &&
-                ((currentValues == null && other.currentValues == null
-                  && currentValue != null && currentValue.equals(other.currentValue))
+                ((mCurrentValues == null && other.mCurrentValues == null
+                  && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
                  ||
-                 (currentValue == null && other.currentValue == null
-                  && currentValues != null && equalArrays(currentValues, other.currentValues)));
+                 (mCurrentValue == null && other.mCurrentValue == null
+                  && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
     }
 
     @Override
     public int hashCode() {
         int result = 17;
-        result = 31 * result + key.hashCode();
-        if (currentValue != null) {
-            result = 31 * result + currentValue.hashCode();
-        } else if (currentValues != null) {
-            for (String value : currentValues) {
+        result = 31 * result + mKey.hashCode();
+        if (mCurrentValue != null) {
+            result = 31 * result + mCurrentValue.hashCode();
+        } else if (mCurrentValues != null) {
+            for (String value : mCurrentValues) {
                 if (value != null) {
                     result = 31 * result + value.hashCode();
                 }
@@ -359,14 +392,14 @@
     }
 
     public RestrictionEntry(Parcel in) {
-        type = in.readInt();
-        key = in.readString();
-        title = in.readString();
-        description = in.readString();
-        choices = readArray(in);
-        values = readArray(in);
-        currentValue = in.readString();
-        currentValues = readArray(in);
+        mType = in.readInt();
+        mKey = in.readString();
+        mTitle = in.readString();
+        mDescription = in.readString();
+        mChoiceEntries = readArray(in);
+        mChoiceValues = readArray(in);
+        mCurrentValue = in.readString();
+        mCurrentValues = readArray(in);
     }
 
     @Override
@@ -387,14 +420,14 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(type);
-        dest.writeString(key);
-        dest.writeString(title);
-        dest.writeString(description);
-        writeArray(dest, choices);
-        writeArray(dest, values);
-        dest.writeString(currentValue);
-        writeArray(dest, currentValues);
+        dest.writeInt(mType);
+        dest.writeString(mKey);
+        dest.writeString(mTitle);
+        dest.writeString(mDescription);
+        writeArray(dest, mChoiceEntries);
+        writeArray(dest, mChoiceValues);
+        dest.writeString(mCurrentValue);
+        writeArray(dest, mCurrentValues);
     }
 
     public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
@@ -409,6 +442,6 @@
 
     @Override
     public String toString() {
-        return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+        return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
     }
 }
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
new file mode 100644
index 0000000..0dd0edd
--- /dev/null
+++ b/core/java/android/content/RestrictionsManager.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for apps to query restrictions imposed by an entity that
+ * manages the user. Apps can also send permission requests to a local or remote
+ * device administrator to override default app-specific restrictions or any other
+ * operation that needs explicit authorization from the administrator.
+ * <p>
+ * Apps can expose a set of restrictions via a runtime receiver mechanism or via
+ * static meta data in the manifest.
+ * <p>
+ * If the user has an active restrictions provider, dynamic requests can be made in
+ * addition to the statically imposed restrictions. Dynamic requests are app-specific
+ * and can be expressed via a predefined set of templates.
+ * <p>
+ * The RestrictionsManager forwards the dynamic requests to the active
+ * restrictions provider. The restrictions provider can respond back to requests by calling
+ * {@link #notifyPermissionResponse(String, Bundle)}, when
+ * a response is received from the administrator of the device or user 
+ * The response is relayed back to the application via a protected broadcast,
+ * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
+ * <p>
+ * Static restrictions are specified by an XML file referenced by a meta-data attribute
+ * in the manifest. This enables applications as well as any web administration consoles
+ * to be able to read the template from the apk.
+ * <p>
+ * The syntax of the XML format is as follows:
+ * <pre>
+ * &lt;restrictions&gt;
+ *     &lt;restriction
+ *         android:key="&lt;key&gt;"
+ *         android:restrictionType="boolean|string|integer|multi-select|null"
+ *         ... /&gt;
+ *     &lt;restriction ... /&gt;
+ * &lt;/restrictions&gt;
+ * </pre>
+ * <p>
+ * The attributes for each restriction depend on the restriction type.
+ *
+ * @see RestrictionEntry
+ */
+public class RestrictionsManager {
+
+    /**
+     * Broadcast intent delivered when a response is received for a permission
+     * request. The response is not available for later query, so the receiver
+     * must persist and/or immediately act upon the response. The application
+     * should not interrupt the user by coming to the foreground if it isn't
+     * currently in the foreground. It can post a notification instead, informing
+     * the user of a change in state.
+     * <p>
+     * For instance, if the user requested permission to make an in-app purchase,
+     * the app can post a notification that the request had been granted or denied,
+     * and allow the purchase to go through.
+     * <p>
+     * The broadcast Intent carries the following extra:
+     * {@link #EXTRA_RESPONSE_BUNDLE}.
+     */
+    public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
+            "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+
+    /**
+     * Protected broadcast intent sent to the active restrictions provider. The intent
+     * contains the following extras:<p>
+     * <ul>
+     * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting
+     * permission.</li>
+     * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li>
+     * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values
+     * for the request.
+     * </ul>
+     * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+     * @see #requestPermission(String, String, Bundle)
+     */
+    public static final String ACTION_REQUEST_PERMISSION =
+            "android.intent.action.REQUEST_PERMISSION";
+
+    /**
+     * The package name of the application making the request.
+     */
+    public static final String EXTRA_PACKAGE_NAME = "package_name";
+
+    /**
+     * The template id that specifies what kind of a request it is and may indicate
+     * how the request is to be presented to the administrator. Must be either one of
+     * the predefined templates or a custom one specified by the application that the
+     * restrictions provider is familiar with.
+     */
+    public static final String EXTRA_TEMPLATE_ID = "template_id";
+
+    /**
+     * A bundle containing the details about the request. The contents depend on the
+     * template id.
+     * @see #EXTRA_TEMPLATE_ID
+     */
+    public static final String EXTRA_REQUEST_BUNDLE = "request_bundle";
+
+    /**
+     * Contains a response from the administrator for specific request.
+     * The bundle contains the following information, at least:
+     * <ul>
+     * <li>{@link #REQUEST_KEY_ID}: The request id.</li>
+     * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li>
+     * </ul>
+     * <p>
+     * And depending on what the request template was, the bundle will contain the actual
+     * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in
+     * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator
+     * approved the request, false otherwise.
+     */
+    public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+
+
+    /**
+     * Request template that presents a simple question, with a possible title and icon.
+     * <p>
+     * Required keys are
+     * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}.
+     * <p>
+     * Optional keys are
+     * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
+     * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
+     */
+    public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+
+    /**
+     * Key for request ID contained in the request bundle.
+     * <p>
+     * App-generated request id to identify the specific request when receiving
+     * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_ID = "android.req_template.req_id";
+
+    /**
+     * Key for request data contained in the request bundle.
+     * <p>
+     * Optional, typically used to identify the specific data that is being referred to,
+     * such as the unique identifier for a movie or book. This is not used for display
+     * purposes and is more like a cookie. This value is returned in the
+     * {@link #EXTRA_RESPONSE_BUNDLE}.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_DATA = "android.req_template.data";
+
+    /**
+     * Key for request title contained in the request bundle.
+     * <p>
+     * Optional, typically used as the title of any notification or dialog presented
+     * to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_TITLE = "android.req_template.title";
+
+    /**
+     * Key for request message contained in the request bundle.
+     * <p>
+     * Required, shown as the actual message in a notification or dialog presented
+     * to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+
+    /**
+     * Key for request icon contained in the request bundle.
+     * <p>
+     * Optional, shown alongside the request message presented to the administrator
+     * who approves the request.
+     * <p>
+     * Type: Bitmap
+     */
+    public static final String REQUEST_KEY_ICON = "android.req_template.icon";
+
+    /**
+     * Key for request approval button label contained in the request bundle.
+     * <p>
+     * Optional, may be shown as a label on the positive button in a dialog or
+     * notification presented to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+
+    /**
+     * Key for request rejection button label contained in the request bundle.
+     * <p>
+     * Optional, may be shown as a label on the negative button in a dialog or
+     * notification presented to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+
+    /**
+     * Key for requestor's name contained in the request bundle. This value is not specified by
+     * the application. It is automatically inserted into the Bundle by the Restrictions Provider
+     * before it is sent to the administrator.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+
+    /**
+     * Key for requestor's device name contained in the request bundle. This value is not specified
+     * by the application. It is automatically inserted into the Bundle by the Restrictions Provider
+     * before it is sent to the administrator.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+
+    /**
+     * Key for the response in the response bundle sent to the application, for a permission
+     * request.
+     * <p>
+     * Type: boolean
+     */
+    public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+
+    private static final String TAG = "RestrictionsManager";
+
+    private final Context mContext;
+    private final IRestrictionsManager mService;
+
+    /**
+     * @hide
+     */
+    public RestrictionsManager(Context context, IRestrictionsManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns any available set of application-specific restrictions applicable
+     * to this application.
+     * @return
+     */
+    public Bundle getApplicationRestrictions() {
+        try {
+            if (mService != null) {
+                return mService.getApplicationRestrictions(mContext.getPackageName());
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+        return null;
+    }
+
+    /**
+     * Called by an application to check if permission requests can be made. If false,
+     * there is no need to request permission for an operation, unless a static
+     * restriction applies to that operation.
+     * @return
+     */
+    public boolean hasRestrictionsProvider() {
+        try {
+            if (mService != null) {
+                return mService.hasRestrictionsProvider();
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+        return false;
+    }
+
+    /**
+     * Called by an application to request permission for an operation. The contents of the
+     * request are passed in a Bundle that contains several pieces of data depending on the
+     * chosen request template.
+     *
+     * @param requestTemplate The request template to use. The template could be one of the
+     * predefined templates specified in this class or a custom template that the specific
+     * Restrictions Provider might understand. For custom templates, the template name should be
+     * namespaced to avoid collisions with predefined templates and templates specified by
+     * other Restrictions Provider vendors.
+     * @param requestData A Bundle containing the data corresponding to the specified request
+     * template. The keys for the data in the bundle depend on the kind of template chosen.
+     */
+    public void requestPermission(String requestTemplate, Bundle requestData) {
+        try {
+            if (mService != null) {
+                mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData);
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+    }
+
+    /**
+     * Called by the Restrictions Provider when a response is available to be
+     * delivered to an application.
+     * @param packageName
+     * @param response
+     */
+    public void notifyPermissionResponse(String packageName, Bundle response) {
+        try {
+            if (mService != null) {
+                mService.notifyPermissionResponse(packageName, response);
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+    }
+
+    /**
+     * Parse and return the list of restrictions defined in the manifest for the specified
+     * package, if any.
+     * @param packageName The application for which to fetch the restrictions list.
+     * @return The list of RestrictionEntry objects created from the XML file specified
+     * in the manifest, or null if none was specified.
+     */
+    public List<RestrictionEntry> getManifestRestrictions(String packageName) {
+        // TODO:
+        return null;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 14141d7..bb6a1cb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -258,6 +258,12 @@
     <protected-broadcast
         android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" />
 
+    <!-- Defined in RestrictionsManager -->
+    <protected-broadcast
+        android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" />
+    <!-- Defined in RestrictionsManager -->
+    <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" />
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
diff --git a/services/Android.mk b/services/Android.mk
index 5fcef64..b4de903 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -25,6 +25,7 @@
     backup \
     devicepolicy \
     print \
+    restrictions \
     usb \
     voiceinteraction
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d78fb13..a52396e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -130,6 +130,8 @@
 
     private static final boolean DBG = false;
 
+    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
+
     final Context mContext;
     final UserManager mUserManager;
     final PowerManager.WakeLock mWakeLock;
@@ -190,6 +192,8 @@
         // This is the list of component allowed to start lock task mode.
         final List<ComponentName> mLockTaskComponents = new ArrayList<ComponentName>();
 
+        ComponentName mRestrictionsProvider;
+
         public DevicePolicyData(int userHandle) {
             mUserHandle = userHandle;
         }
@@ -944,6 +948,10 @@
             out.startDocument(null, true);
 
             out.startTag(null, "policies");
+            if (policy.mRestrictionsProvider != null) {
+                out.attribute(null, ATTR_PERMISSION_PROVIDER,
+                        policy.mRestrictionsProvider.flattenToString());
+            }
 
             final int N = policy.mAdminList.size();
             for (int i=0; i<N; i++) {
@@ -1039,6 +1047,13 @@
                 throw new XmlPullParserException(
                         "Settings do not start with policies tag: found " + tag);
             }
+
+            // Extract the permission provider component name if available
+            String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER);
+            if (permissionProvider != null) {
+                policy.mRestrictionsProvider = ComponentName.unflattenFromString(permissionProvider);
+            }
+
             type = parser.next();
             int outerDepth = parser.getDepth();
             policy.mLockTaskComponents.clear();
@@ -3303,6 +3318,32 @@
         }
     }
 
+    @Override
+    public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            int userHandle = UserHandle.getCallingUserId();
+            DevicePolicyData userData = getUserData(userHandle);
+            userData.mRestrictionsProvider = permissionProvider;
+            saveSettingsLocked(userHandle);
+        }
+    }
+
+    @Override
+    public ComponentName getRestrictionsProvider(int userHandle) {
+        synchronized (this) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Only the system can query the permission provider");
+            }
+            DevicePolicyData userData = getUserData(userHandle);
+            return userData != null ? userData.mRestrictionsProvider : null;
+        }
+    }
+
     public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c0f60c7..27098e7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@
 import com.android.server.pm.UserManagerService;
 import com.android.server.power.PowerManagerService;
 import com.android.server.power.ShutdownThread;
+import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.search.SearchManagerService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
@@ -941,6 +942,12 @@
             }
 
             try {
+                mSystemServiceManager.startService(RestrictionsManagerService.class);
+            } catch (Throwable e) {
+                reportWtf("starting RestrictionsManagerService", e);
+            }
+
+            try {
                 mSystemServiceManager.startService(MediaSessionService.class);
             } catch (Throwable e) {
                 reportWtf("starting MediaSessionService", e);
diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk
new file mode 100644
index 0000000..fcf8626
--- /dev/null
+++ b/services/restrictions/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.restrictions
+
+LOCAL_SRC_FILES += \
+      $(call all-java-files-under,java)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
new file mode 100644
index 0000000..e1f77b3
--- /dev/null
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.restrictions;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.IDevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IRestrictionsManager;
+import android.content.RestrictionsManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IUserManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemService;
+
+/**
+ * SystemService wrapper for the RestrictionsManager implementation. Publishes the
+ * Context.RESTRICTIONS_SERVICE.
+ */
+
+public final class RestrictionsManagerService extends SystemService {
+    private final RestrictionsManagerImpl mRestrictionsManagerImpl;
+
+    public RestrictionsManagerService(Context context) {
+        super(context);
+        mRestrictionsManagerImpl = new RestrictionsManagerImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.RESTRICTIONS_SERVICE, mRestrictionsManagerImpl);
+    }
+
+    class RestrictionsManagerImpl extends IRestrictionsManager.Stub {
+        private final Context mContext;
+        private final IUserManager mUm;
+        private final IDevicePolicyManager mDpm;
+
+        public RestrictionsManagerImpl(Context context) {
+            mContext = context;
+            mUm = (IUserManager) getBinderService(Context.USER_SERVICE);
+            mDpm = (IDevicePolicyManager) getBinderService(Context.DEVICE_POLICY_SERVICE);
+        }
+
+        @Override
+        public Bundle getApplicationRestrictions(String packageName) throws RemoteException {
+            return mUm.getApplicationRestrictions(packageName);
+        }
+
+        @Override
+        public boolean hasRestrictionsProvider() throws RemoteException {
+            int userHandle = UserHandle.getCallingUserId();
+            if (mDpm != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    return mDpm.getRestrictionsProvider(userHandle) != null;
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public void requestPermission(String packageName, String requestTemplate,
+                Bundle requestData) throws RemoteException {
+            int callingUid = Binder.getCallingUid();
+            int userHandle = UserHandle.getUserId(callingUid);
+            if (mDpm != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    ComponentName restrictionsProvider =
+                            mDpm.getRestrictionsProvider(userHandle);
+                    // Check if there is a restrictions provider
+                    if (restrictionsProvider == null) {
+                        throw new IllegalStateException(
+                            "Cannot request permission without a restrictions provider registered");
+                    }
+                    // Check that the packageName matches the caller.
+                    enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" +
+                            " match caller ");
+                    // Prepare and broadcast the intent to the provider
+                    Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION);
+                    intent.setComponent(restrictionsProvider);
+                    intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName);
+                    intent.putExtra(RestrictionsManager.EXTRA_TEMPLATE_ID, requestTemplate);
+                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData);
+                    mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+
+        private void enforceCallerMatchesPackage(int callingUid, String packageName,
+                String message) {
+            try {
+                String[] pkgs = AppGlobals.getPackageManager().getPackagesForUid(callingUid);
+                if (pkgs != null) {
+                    if (!ArrayUtils.contains(pkgs, packageName)) {
+                        throw new SecurityException(message + callingUid);
+                    }
+                }
+            } catch (RemoteException re) {
+                // Shouldn't happen
+            }
+        }
+
+        @Override
+        public void notifyPermissionResponse(String packageName, Bundle response)
+                throws RemoteException {
+            // Check caller
+            int callingUid = Binder.getCallingUid();
+            int userHandle = UserHandle.getUserId(callingUid);
+            if (mDpm != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    ComponentName permProvider = mDpm.getRestrictionsProvider(userHandle);
+                    if (permProvider == null) {
+                        throw new SecurityException("No restrictions provider registered for user");
+                    }
+                    enforceCallerMatchesPackage(callingUid, permProvider.getPackageName(),
+                            "Restrictions provider does not match caller ");
+
+                    // Post the response to target package
+                    Intent responseIntent = new Intent(
+                            RestrictionsManager.ACTION_PERMISSION_RESPONSE_RECEIVED);
+                    responseIntent.setPackage(packageName);
+                    responseIntent.putExtra(RestrictionsManager.EXTRA_RESPONSE_BUNDLE, response);
+                    mContext.sendBroadcastAsUser(responseIntent, new UserHandle(userHandle));
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+    }
+}