/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content; import android.app.admin.DevicePolicyManager; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; import java.util.Collections; import java.util.List; /** * Provides a mechanism for apps to query restrictions imposed by an entity that * manages the user. Apps can also send permission requests to a local or remote * device administrator to override default app-specific restrictions or any other * operation that needs explicit authorization from the administrator. *

* Apps can expose a set of restrictions via an XML file specified in the manifest. *

* 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 request types. *

* The RestrictionsManager forwards the dynamic requests to the active * restrictions provider. The restrictions provider can respond back to requests by calling * {@link #notifyPermissionResponse(String, Bundle)}, when * a response is received from the administrator of the device or user. * The response is relayed back to the application via a protected broadcast, * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}. *

* Prior responses to requests can also be queried through calls to * {@link #getPermissionResponse(String, PermissionResponseCallback)}, if the provider * saves old responses. *

* 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 list of available restrictions from the apk. *

* The syntax of the XML format is as follows: *

 * <restrictions>
 *     <restriction
 *         android:key="<key>"
 *         android:restrictionType="boolean|string|integer|multi-select|null"
 *         ... />
 *     <restriction ... />
 * </restrictions>
 * 
*

* 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 either post a notification informing * the user of the response or wait until the next time the user launches the app. *

* For instance, if the user requested permission to make an in-app purchase, * the app can post a notification that the request had been approved or denied. *

* The broadcast Intent carries the following extra: * {@link #EXTRA_RESPONSE_BUNDLE}. */ public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; /** * The package name of the application making the request. */ public static final String EXTRA_PACKAGE_NAME = "package_name"; /** * Contains a response from the administrator for specific request. * The bundle contains the following information, at least: *

*/ public static final String EXTRA_RESPONSE_BUNDLE = "response"; /** * Request type for a simple question, with a possible title and icon. *

* Required keys are * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}. *

* 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 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. *

* Required keys are * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}. *

* 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. *

* App-generated request ID to identify the specific request when receiving * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. *

* Type: String */ public static final String REQUEST_KEY_ID = "android.request.id"; /** * Key for request data contained in the request bundle. *

* Optional, typically used to identify the specific data that is being referred to, * such as the unique identifier for a movie or book. This is not used for display * purposes and is more like a cookie. This value is returned in the * {@link #EXTRA_RESPONSE_BUNDLE}. *

* Type: String */ public static final String REQUEST_KEY_DATA = "android.request.data"; /** * Key for request title contained in the request bundle. *

* Optional, typically used as the title of any notification or dialog presented * to the administrator who approves the request. *

* Type: String */ public static final String REQUEST_KEY_TITLE = "android.request.title"; /** * Key for request message contained in the request bundle. *

* Required, shown as the actual message in a notification or dialog presented * to the administrator who approves the request. *

* Type: String */ public static final String REQUEST_KEY_MESSAGE = "android.request.mesg"; /** * Key for request icon contained in the request bundle. *

* Optional, shown alongside the request message presented to the administrator * who approves the request. *

* Type: Bitmap */ public static final String REQUEST_KEY_ICON = "android.request.icon"; /** * Key for request approval button label contained in the request bundle. *

* Optional, may be shown as a label on the positive button in a dialog or * notification presented to the administrator who approves the request. *

* Type: String */ public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label"; /** * Key for request rejection button label contained in the request bundle. *

* Optional, may be shown as a label on the negative button in a dialog or * notification presented to the administrator who approves the request. *

* Type: String */ 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 * the application. It is automatically inserted into the Bundle by the Restrictions Provider * before it is sent to the administrator. *

* Type: String */ 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 * by the application. It is automatically inserted into the Bundle by the Restrictions Provider * before it is sent to the administrator. *

* Type: String */ 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. *

* Type: int *

* 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_RESULT = "android.response.result"; /** * 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. *

* 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. *

* 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. *

* 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. *

* Type: int *

* 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. *

* 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. *

* 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) { mContext = context; mService = service; } /** * Returns any available set of application-specific restrictions applicable * to this application. * @return the application restrictions as a Bundle. Returns null if there * are no restrictions. */ public Bundle getApplicationRestrictions() { try { if (mService != null) { return mService.getApplicationRestrictions(mContext.getPackageName()); } } catch (RemoteException re) { Log.w(TAG, "Couldn't reach service"); } return null; } /** * Called by an application to check if 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 { if (mService != null) { return mService.hasRestrictionsProvider(); } } catch (RemoteException re) { Log.w(TAG, "Couldn't reach service"); } return false; } /** * Called by an application to request permission for an operation. The contents of the * request are passed in a Bundle that contains several pieces of data depending on the * chosen request type. * * @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(), requestType, request); } } catch (RemoteException re) { Log.w(TAG, "Couldn't reach service"); } } /** * 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 { if (mService != null) { mService.notifyPermissionResponse(packageName, response); } } catch (RemoteException re) { Log.w(TAG, "Couldn't reach service"); } } /** * Parse and return the list of restrictions defined in the manifest for the specified * package, if any. * * @param packageName The application for which to fetch the restrictions list. * @return The list of RestrictionEntry objects created from the XML file specified * in the manifest, or null if none was specified. */ public List getManifestRestrictions(String packageName) { // 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); } } }