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