API Review: Improvements to RestrictionsManager API
Use an activity intent for local approval instead of a type.
Use PeristableBundle instead of Bundle.
Pass requestId as an explicit argument in cases where it's required.
Bug: 16400892
Change-Id: Id882033f17c39aa9cd63a7eeb73bb7b51f98cf5b
diff --git a/Android.mk b/Android.mk
index 6e51575..79d13d9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -446,6 +446,7 @@
frameworks/base/core/java/android/os/DropBoxManager.aidl \
frameworks/base/core/java/android/os/ParcelFileDescriptor.aidl \
frameworks/base/core/java/android/os/ParcelUuid.aidl \
+ frameworks/base/core/java/android/os/PersistableBundle.aidl \
frameworks/base/core/java/android/print/PrinterInfo.aidl \
frameworks/base/core/java/android/print/PageRange.aidl \
frameworks/base/core/java/android/print/PrintAttributes.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6e25c2a..e6efcea 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6660,7 +6660,7 @@
public abstract class AbstractRestrictionsProvider extends android.content.BroadcastReceiver {
ctor public AbstractRestrictionsProvider();
method public void onReceive(android.content.Context, android.content.Intent);
- method public abstract void requestPermission(android.content.Context, java.lang.String, java.lang.String, android.os.Bundle);
+ method public abstract void requestPermission(android.content.Context, java.lang.String, java.lang.String, java.lang.String, android.os.PersistableBundle);
}
public abstract class AbstractThreadedSyncAdapter {
@@ -8050,14 +8050,17 @@
public class RestrictionsManager {
method public android.os.Bundle getApplicationRestrictions();
+ method public android.content.Intent getLocalApprovalIntent();
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";
+ method public void notifyPermissionResponse(java.lang.String, android.os.PersistableBundle);
+ method public void requestPermission(java.lang.String, java.lang.String, android.os.PersistableBundle);
+ field public static final java.lang.String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.content.action.PERMISSION_RESPONSE_RECEIVED";
+ field public static final java.lang.String ACTION_REQUEST_LOCAL_APPROVAL = "android.content.action.REQUEST_LOCAL_APPROVAL";
field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.content.action.REQUEST_PERMISSION";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
+ field public static final java.lang.String EXTRA_REQUEST_ID = "android.content.extra.REQUEST_ID";
field public static final java.lang.String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
field public static final java.lang.String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS";
@@ -8070,7 +8073,6 @@
field public static final java.lang.String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
field public static final java.lang.String REQUEST_KEY_TITLE = "android.request.title";
field public static final java.lang.String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
- field public static final java.lang.String REQUEST_TYPE_LOCAL_APPROVAL = "android.request.type.local_approval";
field public static final java.lang.String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
field public static final java.lang.String RESPONSE_KEY_MESSAGE = "android.response.msg";
field public static final java.lang.String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
diff --git a/core/java/android/content/AbstractRestrictionsProvider.java b/core/java/android/content/AbstractRestrictionsProvider.java
index 3272970..2b40870 100644
--- a/core/java/android/content/AbstractRestrictionsProvider.java
+++ b/core/java/android/content/AbstractRestrictionsProvider.java
@@ -17,8 +17,8 @@
package android.content;
import android.app.admin.DevicePolicyManager;
-import android.os.Bundle;
import android.os.IBinder;
+import android.os.PersistableBundle;
/**
* Abstract implementation of a Restrictions Provider BroadcastReceiver. To implement a
@@ -30,7 +30,7 @@
* The function of a Restrictions Provider is to transport permission requests from apps on this
* device to an administrator (most likely on a remote device or computer) and deliver back
* responses. The response should be sent back to the app via
- * {@link RestrictionsManager#notifyPermissionResponse(String, Bundle)}.
+ * {@link RestrictionsManager#notifyPermissionResponse(String, PersistableBundle)}.
*
* @see RestrictionsManager
*/
@@ -59,7 +59,7 @@
* @see RestrictionsManager#REQUEST_KEY_ID
*/
public abstract void requestPermission(Context context,
- String packageName, String requestType, Bundle request);
+ String packageName, String requestType, String requestId, PersistableBundle request);
/**
* Intercept standard Restrictions Provider broadcasts. Implementations
@@ -73,8 +73,10 @@
if (RestrictionsManager.ACTION_REQUEST_PERMISSION.equals(action)) {
String packageName = intent.getStringExtra(RestrictionsManager.EXTRA_PACKAGE_NAME);
String requestType = intent.getStringExtra(RestrictionsManager.EXTRA_REQUEST_TYPE);
- Bundle request = intent.getBundleExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE);
- requestPermission(context, packageName, requestType, request);
+ String requestId = intent.getStringExtra(RestrictionsManager.EXTRA_REQUEST_ID);
+ PersistableBundle request = (PersistableBundle)
+ intent.getParcelableExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE);
+ requestPermission(context, packageName, requestType, requestId, request);
}
}
}
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
index b1c0a3a..495ac2e 100644
--- a/core/java/android/content/IRestrictionsManager.aidl
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -16,7 +16,9 @@
package android.content;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.PersistableBundle;
/**
* Interface used by the RestrictionsManager
@@ -25,6 +27,8 @@
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);
+ void requestPermission(in String packageName, in String requestType, in String requestId,
+ in PersistableBundle requestData);
+ void notifyPermissionResponse(in String packageName, in PersistableBundle response);
+ Intent getLocalApprovalIntent();
}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 5ae10cfc..c1226c0 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -16,6 +16,7 @@
package android.content;
+import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -23,6 +24,7 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
@@ -51,7 +53,7 @@
* <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
+ * {@link #notifyPermissionResponse(String, PersistableBundle)}, 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}.
@@ -126,14 +128,15 @@
* {@link #EXTRA_RESPONSE_BUNDLE}.
*/
public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
- "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+ "android.content.action.PERMISSION_RESPONSE_RECEIVED";
/**
* Broadcast intent sent to the Restrictions Provider to handle a permission request from
* an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME},
- * {@link #EXTRA_REQUEST_TYPE} and {@link #EXTRA_REQUEST_BUNDLE}. The Restrictions Provider
- * will handle the request and respond back to the RestrictionsManager, when a response is
- * available, by calling {@link #notifyPermissionResponse}.
+ * {@link #EXTRA_REQUEST_TYPE}, {@link #EXTRA_REQUEST_ID} and {@link #EXTRA_REQUEST_BUNDLE}.
+ * The Restrictions Provider will handle the request and respond back to the
+ * RestrictionsManager, when a response is available, by calling
+ * {@link #notifyPermissionResponse}.
* <p>
* The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN}
* permission to ensure that only the system can send the broadcast.
@@ -142,17 +145,45 @@
"android.content.action.REQUEST_PERMISSION";
/**
+ * Activity intent that is optionally implemented by the Restrictions Provider package
+ * to challenge for an administrator PIN or password locally on the device. Apps will
+ * call this intent using {@link Activity#startActivityForResult}. On a successful
+ * response, {@link Activity#onActivityResult} will return a resultCode of
+ * {@link Activity#RESULT_OK}.
+ * <p>
+ * The intent must contain {@link #EXTRA_REQUEST_BUNDLE} as an extra and the bundle must
+ * contain at least {@link #REQUEST_KEY_MESSAGE} for the activity to display.
+ * <p>
+ * @see #getLocalApprovalIntent()
+ */
+ public static final String ACTION_REQUEST_LOCAL_APPROVAL =
+ "android.content.action.REQUEST_LOCAL_APPROVAL";
+
+ /**
* The package name of the application making the request.
+ * <p>
+ * Type: String
*/
public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
/**
* The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ * <p>
+ * Type: String
*/
public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
/**
+ * The request ID passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ * <p>
+ * Type: String
+ */
+ public static final String EXTRA_REQUEST_ID = "android.content.extra.REQUEST_ID";
+
+ /**
* The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ * <p>
+ * Type: {@link PersistableBundle}
*/
public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
@@ -163,14 +194,15 @@
* <li>{@link #REQUEST_KEY_ID}: The request ID.</li>
* <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li>
* </ul>
+ * <p>
+ * Type: {@link PersistableBundle}
*/
public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
/**
* Request type for a simple question, with a possible title and icon.
* <p>
- * Required keys are
- * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}.
+ * Required keys are: {@link #REQUEST_KEY_MESSAGE}
* <p>
* Optional keys are
* {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
@@ -179,22 +211,6 @@
public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
/**
- * Request type for a local password challenge. This is a way for an app to ask
- * the administrator to override an operation that is restricted on the device, such
- * as configuring Wi-Fi access points. It is most useful for situations where there
- * is no network connectivity for a remote administrator's response. The normal user of the
- * device is not expected to know the password. The challenge is meant for the administrator.
- * <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_TYPE_LOCAL_APPROVAL = "android.request.type.local_approval";
-
- /**
* Key for request ID contained in the request bundle.
* <p>
* App-generated request ID to identify the specific request when receiving
@@ -240,9 +256,10 @@
* Key for request icon contained in the request bundle.
* <p>
* Optional, shown alongside the request message presented to the administrator
- * who approves the request.
+ * who approves the request. The content must be a compressed image such as a
+ * PNG or JPEG, as a byte array.
* <p>
- * Type: Bitmap
+ * Type: byte[]
*/
public static final String REQUEST_KEY_ICON = "android.request.icon";
@@ -403,7 +420,7 @@
/**
* Called by an application to check if there is an active Restrictions Provider. If
- * there isn't, {@link #requestPermission(String, Bundle)} is not available.
+ * there isn't, {@link #requestPermission(String, String, PersistableBundle)} is not available.
*
* @return whether there is an active Restrictions Provider.
*/
@@ -428,40 +445,54 @@
* Restrictions Provider might understand. For custom types, the type name should be
* namespaced to avoid collisions with predefined types and types specified by
* other Restrictions Providers.
- * @param request A Bundle containing the data corresponding to the specified request
+ * @param requestId A unique id generated by the app that contains sufficient information
+ * to identify the parameters of the request when it receives the id in the response.
+ * @param request A PersistableBundle containing the data corresponding to the specified request
* type. The keys for the data in the bundle depend on the request type.
*
* @throws IllegalArgumentException if any of the required parameters are missing.
*/
- public void requestPermission(String requestType, Bundle request) {
+ public void requestPermission(String requestType, String requestId, PersistableBundle request) {
if (requestType == null) {
throw new NullPointerException("requestType cannot be null");
}
+ if (requestId == null) {
+ throw new NullPointerException("requestId cannot be null");
+ }
if (request == null) {
throw new NullPointerException("request cannot be null");
}
- if (!request.containsKey(REQUEST_KEY_ID)) {
- throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
- }
try {
if (mService != null) {
- mService.requestPermission(mContext.getPackageName(), requestType, request);
+ mService.requestPermission(mContext.getPackageName(), requestType, requestId,
+ request);
}
} catch (RemoteException re) {
Log.w(TAG, "Couldn't reach service");
}
}
+ public Intent getLocalApprovalIntent() {
+ try {
+ if (mService != null) {
+ return mService.getLocalApprovalIntent();
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return null;
+ }
+
/**
* Called by the Restrictions Provider to deliver a response to an application.
*
* @param packageName the application to deliver the response to. Cannot be null.
- * @param response the Bundle containing the response status, request ID and other information.
+ * @param response the bundle containing the response status, request ID and other information.
* Cannot be null.
*
* @throws IllegalArgumentException if any of the required parameters are missing.
*/
- public void notifyPermissionResponse(String packageName, Bundle response) {
+ public void notifyPermissionResponse(String packageName, PersistableBundle response) {
if (packageName == null) {
throw new NullPointerException("packageName cannot be null");
}
diff --git a/core/java/android/os/PersistableBundle.aidl b/core/java/android/os/PersistableBundle.aidl
new file mode 100644
index 0000000..5b05873
--- /dev/null
+++ b/core/java/android/os/PersistableBundle.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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.os;
+
+parcelable PersistableBundle;
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
index 4fe30e6..fb29b6a 100644
--- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -40,6 +40,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.IUserManager;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -103,7 +104,8 @@
@Override
public void requestPermission(final String packageName, final String requestType,
- final Bundle requestData) throws RemoteException {
+ final String requestId,
+ final PersistableBundle requestData) throws RemoteException {
if (DEBUG) {
Log.i(LOG_TAG, "requestPermission");
}
@@ -127,6 +129,7 @@
intent.setComponent(restrictionsProvider);
intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RestrictionsManager.EXTRA_REQUEST_TYPE, requestType);
+ intent.putExtra(RestrictionsManager.EXTRA_REQUEST_ID, requestId);
intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData);
mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
} finally {
@@ -136,7 +139,40 @@
}
@Override
- public void notifyPermissionResponse(String packageName, Bundle response)
+ public Intent getLocalApprovalIntent() throws RemoteException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "requestPermission");
+ }
+ final int userHandle = UserHandle.getCallingUserId();
+ 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");
+ }
+ String providerPackageName = restrictionsProvider.getPackageName();
+ Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_LOCAL_APPROVAL);
+ intent.setPackage(providerPackageName);
+ ResolveInfo ri = AppGlobals.getPackageManager().resolveIntent(intent,
+ null /* resolvedType */, 0 /* flags */, userHandle);
+ if (ri != null && ri.activityInfo != null && ri.activityInfo.exported) {
+ intent.setComponent(new ComponentName(ri.activityInfo.packageName,
+ ri.activityInfo.name));
+ return intent;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void notifyPermissionResponse(String packageName, PersistableBundle response)
throws RemoteException {
// Check caller
int callingUid = Binder.getCallingUid();