summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--api/current.txt52
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java11
-rw-r--r--core/java/android/content/AbstractRestrictionsProvider.java93
-rw-r--r--core/java/android/content/IPermissionResponseCallback.aidl30
-rw-r--r--core/java/android/content/IRestrictionsManager.aidl3
-rw-r--r--core/java/android/content/IRestrictionsProvider.aidl31
-rw-r--r--core/java/android/content/RestrictionsManager.java287
-rw-r--r--services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java171
9 files changed, 559 insertions, 121 deletions
diff --git a/Android.mk b/Android.mk
index 589279dd7857..23c4753cfa22 100644
--- a/Android.mk
+++ b/Android.mk
@@ -118,7 +118,9 @@ LOCAL_SRC_FILES += \
core/java/android/content/IIntentReceiver.aidl \
core/java/android/content/IIntentSender.aidl \
core/java/android/content/IOnPrimaryClipChangedListener.aidl \
+ core/java/android/content/IPermissionResponseCallback.aidl \
core/java/android/content/IRestrictionsManager.aidl \
+ core/java/android/content/IRestrictionsProvider.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 db9a27ce2fde..c319ce8b8ccd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6570,6 +6570,13 @@ package android.bluetooth.le {
package android.content {
+ public abstract class AbstractRestrictionsProvider extends android.app.Service {
+ ctor public AbstractRestrictionsProvider();
+ method public abstract android.os.Bundle getPermissionResponse(java.lang.String, java.lang.String);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void requestPermission(java.lang.String, java.lang.String, android.os.Bundle);
+ }
+
public abstract class AbstractThreadedSyncAdapter {
ctor public AbstractThreadedSyncAdapter(android.content.Context, boolean);
ctor public AbstractThreadedSyncAdapter(android.content.Context, boolean, boolean);
@@ -7949,26 +7956,41 @@ package android.content {
public class RestrictionsManager {
method public android.os.Bundle getApplicationRestrictions();
method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
+ method public void getPermissionResponse(java.lang.String, android.content.RestrictionsManager.PermissionResponseCallback);
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";
+ field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response";
+ field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label";
+ field public static final java.lang.String REQUEST_KEY_DATA = "android.request.data";
+ field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
+ field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.request.device";
+ field public static final java.lang.String REQUEST_KEY_ICON = "android.request.icon";
+ field public static final java.lang.String REQUEST_KEY_ID = "android.request.id";
+ field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.request.mesg";
+ field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.request.requestor";
+ field public static final java.lang.String REQUEST_KEY_TITLE = "android.request.title";
+ field public static final java.lang.String REQUEST_TYPE_LOCAL_APPROVAL = "android.request.type.local_approval";
+ field public static final java.lang.String REQUEST_TYPE_QUESTION = "android.request.type.question";
+ field public static final java.lang.String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
+ field public static final java.lang.String RESPONSE_KEY_ERROR_MESSAGE = "android.response.errormsg";
+ field public static final java.lang.String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
+ field public static final java.lang.String RESPONSE_KEY_RESULT = "android.response.result";
+ field public static final int RESULT_APPROVED = 1; // 0x1
+ field public static final int RESULT_DENIED = 2; // 0x2
+ field public static final int RESULT_ERROR = 5; // 0x5
+ field public static final int RESULT_ERROR_BAD_REQUEST = 1; // 0x1
+ field public static final int RESULT_ERROR_INTERNAL = 3; // 0x3
+ field public static final int RESULT_ERROR_NETWORK = 2; // 0x2
+ field public static final int RESULT_NO_RESPONSE = 3; // 0x3
+ field public static final int RESULT_UNKNOWN_REQUEST = 4; // 0x4
+ }
+
+ public static abstract class RestrictionsManager.PermissionResponseCallback {
+ ctor public RestrictionsManager.PermissionResponseCallback();
+ method public abstract void onResponse(android.os.Bundle);
}
public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6ea6b4b26063..77906405d187 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -19,6 +19,7 @@ package android.app.admin;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
+import android.content.AbstractRestrictionsProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -2661,19 +2662,19 @@ public class DevicePolicyManager {
}
/**
- * Designates a specific broadcast receiver component as the provider for
+ * Designates a specific service 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,
+ * @param provider The component name of the service that implements
+ * {@link AbstractRestrictionsProvider}. If this param is null,
* it removes the restrictions provider previously assigned.
*/
- public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) {
+ public void setRestrictionsProvider(ComponentName admin, ComponentName provider) {
if (mService != null) {
try {
- mService.setRestrictionsProvider(admin, receiver);
+ mService.setRestrictionsProvider(admin, provider);
} catch (RemoteException re) {
Log.w(TAG, "Failed to set permission provider on device policy service");
}
diff --git a/core/java/android/content/AbstractRestrictionsProvider.java b/core/java/android/content/AbstractRestrictionsProvider.java
new file mode 100644
index 000000000000..1119478b9932
--- /dev/null
+++ b/core/java/android/content/AbstractRestrictionsProvider.java
@@ -0,0 +1,93 @@
+/*
+ * 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.Service;
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * Abstract implementation of a Restrictions Provider Service. To implement a Restrictions Provider,
+ * extend from this class and implement the abstract methods. Export this service in the
+ * manifest. A profile owner device admin can then register this component as a Restrictions
+ * Provider using {@link DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)}.
+ * <p>
+ * 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)}.
+ * <p>
+ * Apps can also query previously received responses using
+ * {@link #getPermissionResponse(String, String)}. The period for which previously received
+ * responses are available is left to the implementation of the Restrictions Provider.
+ */
+public abstract class AbstractRestrictionsProvider extends Service {
+
+ private static final String TAG = "AbstractRestrictionsProvider";
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new RestrictionsProviderWrapper().asBinder();
+ }
+
+ /**
+ * Checks to see if there is a response for a prior request and returns the response bundle if
+ * it exists. If there is no response yet or if the request is not known, the returned bundle
+ * should contain the response code in {@link RestrictionsManager#RESPONSE_KEY_RESULT}.
+ *
+ * @param packageName the application that is requesting a permission response.
+ * @param requestId the id of the request for which the response is needed.
+ * @return a bundle containing at a minimum the result of the request. It could contain other
+ * optional information such as error codes and cookies.
+ *
+ * @see RestrictionsManager#RESPONSE_KEY_RESULT
+ */
+ public abstract Bundle getPermissionResponse(String packageName, String requestId);
+
+ /**
+ * An asynchronous permission request made by an application for an operation that requires
+ * authorization by a local or remote administrator other than the user. The Restrictions
+ * Provider must transfer the request to the administrator and deliver back a response, when
+ * available. The calling application is aware that the response could take an indefinite
+ * amount of time.
+ *
+ * @param packageName the application requesting permission.
+ * @param requestType the type of request, which determines the content and presentation of
+ * the request data.
+ * @param request the request data bundle containing at a minimum a request id.
+ *
+ * @see RestrictionsManager#REQUEST_TYPE_QUESTION
+ * @see RestrictionsManager#REQUEST_TYPE_LOCAL_APPROVAL
+ * @see RestrictionsManager#REQUEST_KEY_ID
+ */
+ public abstract void requestPermission(String packageName, String requestType, Bundle request);
+
+ private class RestrictionsProviderWrapper extends IRestrictionsProvider.Stub {
+
+ @Override
+ public Bundle getPermissionResponse(String packageName, String requestId) {
+ return AbstractRestrictionsProvider.this
+ .getPermissionResponse(packageName, requestId);
+ }
+
+ @Override
+ public void requestPermission(String packageName, String templateId, Bundle request) {
+ AbstractRestrictionsProvider.this.requestPermission(packageName, templateId, request);
+ }
+ }
+}
diff --git a/core/java/android/content/IPermissionResponseCallback.aidl b/core/java/android/content/IPermissionResponseCallback.aidl
new file mode 100644
index 000000000000..8309768957ba
--- /dev/null
+++ b/core/java/android/content/IPermissionResponseCallback.aidl
@@ -0,0 +1,30 @@
+/*
+** 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.content;
+
+import android.os.Bundle;
+
+/**
+ * Callback for permission response queries.
+ *
+ * @hide
+ */
+ interface IPermissionResponseCallback {
+
+ void onResponse(in Bundle response);
+
+}
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
index b1c0a3afd9b4..49eb65bd74d0 100644
--- a/core/java/android/content/IRestrictionsManager.aidl
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -17,6 +17,7 @@
package android.content;
import android.os.Bundle;
+import android.content.IPermissionResponseCallback;
/**
* Interface used by the RestrictionsManager
@@ -27,4 +28,6 @@ interface IRestrictionsManager {
boolean hasRestrictionsProvider();
void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
void notifyPermissionResponse(in String packageName, in Bundle response);
+ void getPermissionResponse(in String packageName, in String requestId,
+ in IPermissionResponseCallback callback);
}
diff --git a/core/java/android/content/IRestrictionsProvider.aidl b/core/java/android/content/IRestrictionsProvider.aidl
new file mode 100644
index 000000000000..4506b72aa200
--- /dev/null
+++ b/core/java/android/content/IRestrictionsProvider.aidl
@@ -0,0 +1,31 @@
+/*
+** 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.content;
+
+import android.os.Bundle;
+
+/**
+ * Interface to a restrictions provider service component.
+ *
+ * @hide
+ */
+ interface IRestrictionsProvider {
+
+ void requestPermission(in String packageName, in String requestType, in Bundle requestBundle);
+ Bundle getPermissionResponse(in String packageName, in String requestId);
+
+}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 0dd0edde12a5..5ef2dbc39bdb 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -30,23 +30,26 @@ import java.util.List;
* 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.
+ * Apps can expose a set of restrictions via an XML file specified 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.
+ * and can be expressed via a predefined set of request types.
* <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
+ * 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>
+ * Prior responses to requests can also be queried through calls to
+ * {@link #getPermissionResponse(String, PermissionResponseCallback)}, if the provider
+ * saves old responses.
+ * <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.
+ * to be able to read the list of available restrictions from the apk.
* <p>
* The syntax of the XML format is as follows:
* <pre>
@@ -62,20 +65,22 @@ import java.util.List;
* The attributes for each restriction depend on the restriction type.
*
* @see RestrictionEntry
+ * @see AbstractRestrictionsProvider
*/
public class RestrictionsManager {
+ private static final String TAG = "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.
+ * currently in the foreground. It can either post a notification informing
+ * the user of the response or wait until the next time the user launches the app.
* <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.
+ * the app can post a notification that the request had been approved or denied.
* <p>
* The broadcast Intent carries the following extra:
* {@link #EXTRA_RESPONSE_BUNDLE}.
@@ -84,59 +89,38 @@ public class RestrictionsManager {
"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>
+ * <li>{@link #REQUEST_KEY_ID}: The request ID.</li>
+ * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</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";
+ public static final String EXTRA_RESPONSE_BUNDLE = "response";
+ /**
+ * 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}.
+ * <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_QUESTION = "android.request.type.question";
/**
- * Request template that presents a simple question, with a possible title and icon.
+ * 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}.
@@ -145,17 +129,17 @@ public class RestrictionsManager {
* {@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";
+ 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
+ * 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";
+ public static final String REQUEST_KEY_ID = "android.request.id";
/**
* Key for request data contained in the request bundle.
@@ -167,7 +151,7 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_DATA = "android.req_template.data";
+ public static final String REQUEST_KEY_DATA = "android.request.data";
/**
* Key for request title contained in the request bundle.
@@ -177,7 +161,7 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_TITLE = "android.req_template.title";
+ public static final String REQUEST_KEY_TITLE = "android.request.title";
/**
* Key for request message contained in the request bundle.
@@ -187,7 +171,7 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+ public static final String REQUEST_KEY_MESSAGE = "android.request.mesg";
/**
* Key for request icon contained in the request bundle.
@@ -197,7 +181,7 @@ public class RestrictionsManager {
* <p>
* Type: Bitmap
*/
- public static final String REQUEST_KEY_ICON = "android.req_template.icon";
+ public static final String REQUEST_KEY_ICON = "android.request.icon";
/**
* Key for request approval button label contained in the request bundle.
@@ -207,7 +191,7 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+ public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label";
/**
* Key for request rejection button label contained in the request bundle.
@@ -217,7 +201,7 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+ public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
/**
* Key for requestor's name contained in the request bundle. This value is not specified by
@@ -226,7 +210,7 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+ public static final String REQUEST_KEY_REQUESTOR_NAME = "android.request.requestor";
/**
* Key for requestor's device name contained in the request bundle. This value is not specified
@@ -235,22 +219,112 @@ public class RestrictionsManager {
* <p>
* Type: String
*/
- public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+ public static final String REQUEST_KEY_DEVICE_NAME = "android.request.device";
/**
* Key for the response in the response bundle sent to the application, for a permission
* request.
* <p>
- * Type: boolean
+ * Type: int
+ * <p>
+ * Possible values: {@link #RESULT_APPROVED}, {@link #RESULT_DENIED},
+ * {@link #RESULT_NO_RESPONSE}, {@link #RESULT_UNKNOWN_REQUEST} or
+ * {@link #RESULT_ERROR}.
*/
- public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+ public static final String RESPONSE_KEY_RESULT = "android.response.result";
- private static final String TAG = "RestrictionsManager";
+ /**
+ * Response result value indicating that the request was approved.
+ */
+ public static final int RESULT_APPROVED = 1;
+
+ /**
+ * Response result value indicating that the request was denied.
+ */
+ public static final int RESULT_DENIED = 2;
+
+ /**
+ * Response result value indicating that the request has not received a response yet.
+ */
+ public static final int RESULT_NO_RESPONSE = 3;
+
+ /**
+ * Response result value indicating that the request is unknown, when returned through a
+ * call to #getPendingResponse
+ */
+ public static final int RESULT_UNKNOWN_REQUEST = 4;
+
+ /**
+ * Response result value indicating an error condition. Additional error code might be available
+ * in the response bundle, for the key {@link #RESPONSE_KEY_ERROR_CODE}. There might also be
+ * an associated error message in the response bundle, for the key
+ * {@link #RESPONSE_KEY_ERROR_MESSAGE}.
+ */
+ public static final int RESULT_ERROR = 5;
+
+ /**
+ * Error code indicating that there was a problem with the request.
+ * <p>
+ * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
+ */
+ public static final int RESULT_ERROR_BAD_REQUEST = 1;
+
+ /**
+ * Error code indicating that there was a problem with the network.
+ * <p>
+ * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
+ */
+ public static final int RESULT_ERROR_NETWORK = 2;
+
+ /**
+ * Error code indicating that there was an internal error.
+ * <p>
+ * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
+ */
+ public static final int RESULT_ERROR_INTERNAL = 3;
+
+ /**
+ * Key for the optional error code in the response bundle sent to the application.
+ * <p>
+ * Type: int
+ * <p>
+ * Possible values: {@link #RESULT_ERROR_BAD_REQUEST}, {@link #RESULT_ERROR_NETWORK} or
+ * {@link #RESULT_ERROR_INTERNAL}.
+ */
+ public static final String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
+
+ /**
+ * Key for the optional error message in the response bundle sent to the application.
+ * <p>
+ * Type: String
+ */
+ public static final String RESPONSE_KEY_ERROR_MESSAGE = "android.response.errormsg";
+
+ /**
+ * Key for the optional timestamp of when the administrator responded to the permission
+ * request. It is an represented in milliseconds since January 1, 1970 00:00:00.0 UTC.
+ * <p>
+ * Type: long
+ */
+ public static final String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
private final Context mContext;
private final IRestrictionsManager mService;
/**
+ * Callback object for returning a response for a request.
+ *
+ * @see #getPermissionResponse
+ */
+ public static abstract class PermissionResponseCallback {
+ /**
+ * Contains the response
+ * @param response
+ */
+ public abstract void onResponse(Bundle response);
+ }
+
+ /**
* @hide
*/
public RestrictionsManager(Context context, IRestrictionsManager service) {
@@ -261,7 +335,8 @@ public class RestrictionsManager {
/**
* Returns any available set of application-specific restrictions applicable
* to this application.
- * @return
+ * @return the application restrictions as a Bundle. Returns null if there
+ * are no restrictions.
*/
public Bundle getApplicationRestrictions() {
try {
@@ -275,10 +350,11 @@ public class RestrictionsManager {
}
/**
- * 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
+ * Called by an application to check if there is an active restrictions provider. If
+ * there isn't, {@link #getPermissionResponse(String, PermissionResponseCallback)}
+ * and {@link #requestPermission(String, Bundle)} are not available.
+ *
+ * @return whether there is an active restrictions provider.
*/
public boolean hasRestrictionsProvider() {
try {
@@ -294,20 +370,20 @@ public class RestrictionsManager {
/**
* 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.
+ * chosen request type.
*
- * @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) {
+ * @param requestType The type of request. The type could be one of the
+ * predefined types specified here or a custom type that the specific
+ * 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
+ * type. The keys for the data in the bundle depend on the request type.
+ */
+ public void requestPermission(String requestType, Bundle request) {
try {
if (mService != null) {
- mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData);
+ mService.requestPermission(mContext.getPackageName(), requestType, request);
}
} catch (RemoteException re) {
Log.w(TAG, "Couldn't reach service");
@@ -315,10 +391,39 @@ public class RestrictionsManager {
}
/**
- * Called by the Restrictions Provider when a response is available to be
- * delivered to an application.
- * @param packageName
- * @param response
+ * Called by an application to query for any available response from the restrictions provider
+ * for the given requestId. The call returns immediately and the response will be returned
+ * via the provided callback. This does not initiate a new request and does not wait
+ * for a response to be received. It merely returns any previously received response
+ * or indicates if there was no available response. If there are multiple responses
+ * available for the same request ID, the most recent one is returned.
+ *
+ * @param requestId The ID of the original request made via
+ * {@link #requestPermission(String, Bundle)}. It's possible to also query for responses
+ * to requests made on a different device with the same requestId, if the Restrictions
+ * Provider happens to sync responses across devices with the same account managed by the
+ * restrictions provider.
+ * @param callback The response is returned via the callback object. Cannot be null.
+ */
+ public void getPermissionResponse(String requestId, PermissionResponseCallback callback) {
+ if (requestId == null || callback == null) {
+ throw new NullPointerException("requestId or callback cannot be null");
+ }
+ try {
+ if (mService != null) {
+ mService.getPermissionResponse(mContext.getPackageName(), requestId,
+ new PermissionResponseCallbackWrapper(callback));
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Called by the restrictions provider to deliver a response to an application.
+ *
+ * @param packageName the application to deliver the response to.
+ * @param response the Bundle containing the response status, request ID and other information.
*/
public void notifyPermissionResponse(String packageName, Bundle response) {
try {
@@ -333,6 +438,7 @@ public class RestrictionsManager {
/**
* 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.
@@ -341,4 +447,19 @@ public class RestrictionsManager {
// TODO:
return null;
}
+
+ private static class PermissionResponseCallbackWrapper
+ extends IPermissionResponseCallback.Stub {
+
+ private PermissionResponseCallback mCallback;
+
+ PermissionResponseCallbackWrapper(PermissionResponseCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse(Bundle response) {
+ mCallback.onResponse(response);
+ }
+ }
}
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
index e1f77b3a68b1..1db3c5ed6a81 100644
--- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -17,18 +17,23 @@
package com.android.server.restrictions;
import android.Manifest;
+import android.accounts.IAccountAuthenticator;
import android.app.AppGlobals;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.IDevicePolicyManager;
+import android.content.AbstractRestrictionsProvider;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.IPermissionResponseCallback;
+import android.content.IRestrictionsProvider;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -37,11 +42,13 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.IUserManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;
@@ -50,8 +57,11 @@ import com.android.server.SystemService;
* SystemService wrapper for the RestrictionsManager implementation. Publishes the
* Context.RESTRICTIONS_SERVICE.
*/
-
public final class RestrictionsManagerService extends SystemService {
+
+ static final String LOG_TAG = "RestrictionsManagerService";
+ static final boolean DEBUG = false;
+
private final RestrictionsManagerImpl mRestrictionsManagerImpl;
public RestrictionsManagerService(Context context) {
@@ -65,7 +75,7 @@ public final class RestrictionsManagerService extends SystemService {
}
class RestrictionsManagerImpl extends IRestrictionsManager.Stub {
- private final Context mContext;
+ final Context mContext;
private final IUserManager mUm;
private final IDevicePolicyManager mDpm;
@@ -96,8 +106,11 @@ public final class RestrictionsManagerService extends SystemService {
}
@Override
- public void requestPermission(String packageName, String requestTemplate,
- Bundle requestData) throws RemoteException {
+ public void requestPermission(final String packageName, final String requestType,
+ final Bundle requestData) throws RemoteException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "requestPermission");
+ }
int callingUid = Binder.getCallingUid();
int userHandle = UserHandle.getUserId(callingUid);
if (mDpm != null) {
@@ -114,29 +127,61 @@ public final class RestrictionsManagerService extends SystemService {
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 intent = new Intent();
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));
+ new ProviderServiceConnection(intent, null, userHandle) {
+ @Override
+ public void run() throws RemoteException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "calling requestPermission for " + packageName
+ + ", type=" + requestType + ", data=" + requestData);
+ }
+ mRestrictionsProvider.requestPermission(packageName,
+ requestType, requestData);
+ }
+ }.bind();
} 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);
+ @Override
+ public void getPermissionResponse(final String packageName, final String requestId,
+ final IPermissionResponseCallback callback) 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 fetch 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();
+ intent.setComponent(restrictionsProvider);
+ new ProviderServiceConnection(intent, callback.asBinder(), userHandle) {
+ @Override
+ public void run() throws RemoteException {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "calling getPermissionResponse for " + packageName
+ + ", id=" + requestId);
+ }
+ Bundle response = mRestrictionsProvider.getPermissionResponse(
+ packageName, requestId);
+ callback.onResponse(response);
+ }
+ }.bind();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } catch (RemoteException re) {
- // Shouldn't happen
}
}
@@ -167,5 +212,95 @@ public final class RestrictionsManagerService extends SystemService {
}
}
}
+
+ 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
+ }
+ }
+
+ abstract class ProviderServiceConnection
+ implements IBinder.DeathRecipient, ServiceConnection {
+
+ protected IRestrictionsProvider mRestrictionsProvider;
+ private Intent mIntent;
+ protected int mUserHandle;
+ protected IBinder mResponse;
+ private boolean mAbort;
+
+ public ProviderServiceConnection(Intent intent, IBinder response, int userHandle) {
+ mIntent = intent;
+ mResponse = response;
+ mUserHandle = userHandle;
+ if (mResponse != null) {
+ try {
+ mResponse.linkToDeath(this, 0 /* flags */);
+ } catch (RemoteException re) {
+ close();
+ }
+ }
+ }
+
+ /** Bind to the RestrictionsProvider process */
+ public void bind() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "binding to service: " + mIntent);
+ }
+ mContext.bindServiceAsUser(mIntent, this, Context.BIND_AUTO_CREATE,
+ new UserHandle(mUserHandle));
+ }
+
+ private void close() {
+ mAbort = true;
+ unbind();
+ }
+
+ private void unbind() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "unbinding from service");
+ }
+ mContext.unbindService(this);
+ }
+
+ /** Implement this to call the appropriate method on the service */
+ public abstract void run() throws RemoteException;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "connected to " + name);
+ }
+ mRestrictionsProvider = IRestrictionsProvider.Stub.asInterface(service);
+ if (!mAbort) {
+ try {
+ run();
+ } catch (RemoteException re) {
+ Log.w("RestrictionsProvider", "Remote exception: " + re);
+ }
+ }
+ close();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "disconnected from " + name);
+ }
+ mRestrictionsProvider = null;
+ }
+
+ @Override
+ public void binderDied() {
+ mAbort = true;
+ }
+ }
}
}