diff options
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | api/current.txt | 52 | ||||
-rw-r--r-- | core/java/android/app/admin/DevicePolicyManager.java | 11 | ||||
-rw-r--r-- | core/java/android/content/AbstractRestrictionsProvider.java | 93 | ||||
-rw-r--r-- | core/java/android/content/IPermissionResponseCallback.aidl | 30 | ||||
-rw-r--r-- | core/java/android/content/IRestrictionsManager.aidl | 3 | ||||
-rw-r--r-- | core/java/android/content/IRestrictionsProvider.aidl | 31 | ||||
-rw-r--r-- | core/java/android/content/RestrictionsManager.java | 287 | ||||
-rw-r--r-- | services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java | 171 |
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; + } + } } } |