blob: 6893067e060b3d2b9c718f8613759a0e7e056297 [file] [log] [blame]
Amith Yamasanif20d6402014-05-24 15:34:37 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content;
18
Amith Yamasani9c449332014-07-18 15:19:22 -070019import android.app.Activity;
Amith Yamasanif20d6402014-05-24 15:34:37 -070020import android.app.admin.DevicePolicyManager;
Amith Yamasanic8c84252014-07-13 17:12:12 -070021import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.content.res.TypedArray;
25import android.content.res.XmlResourceParser;
Amith Yamasanif20d6402014-05-24 15:34:37 -070026import android.os.Bundle;
Amith Yamasani9c449332014-07-18 15:19:22 -070027import android.os.PersistableBundle;
Amith Yamasanif20d6402014-05-24 15:34:37 -070028import android.os.RemoteException;
Amith Yamasanid1d7c022014-08-19 17:03:41 -070029import android.service.restrictions.RestrictionsReceiver;
Amith Yamasanic8c84252014-07-13 17:12:12 -070030import android.util.AttributeSet;
Amith Yamasanif20d6402014-05-24 15:34:37 -070031import android.util.Log;
Amith Yamasanic8c84252014-07-13 17:12:12 -070032import android.util.Xml;
Amith Yamasanif20d6402014-05-24 15:34:37 -070033
Amith Yamasanic8c84252014-07-13 17:12:12 -070034import com.android.internal.R;
Fyodor Kupolov262f9952015-03-23 18:55:11 -070035import com.android.internal.util.XmlUtils;
Amith Yamasanic8c84252014-07-13 17:12:12 -070036
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40import java.io.IOException;
41import java.util.ArrayList;
Fyodor Kupolov262f9952015-03-23 18:55:11 -070042import java.util.Arrays;
Amith Yamasanif20d6402014-05-24 15:34:37 -070043import java.util.List;
44
45/**
46 * Provides a mechanism for apps to query restrictions imposed by an entity that
47 * manages the user. Apps can also send permission requests to a local or remote
48 * device administrator to override default app-specific restrictions or any other
49 * operation that needs explicit authorization from the administrator.
50 * <p>
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -070051 * Apps can expose a set of restrictions via an XML file specified in the manifest.
Amith Yamasanif20d6402014-05-24 15:34:37 -070052 * <p>
Amith Yamasani5470bc12014-07-14 17:38:27 -070053 * If the user has an active Restrictions Provider, dynamic requests can be made in
Amith Yamasanif20d6402014-05-24 15:34:37 -070054 * addition to the statically imposed restrictions. Dynamic requests are app-specific
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -070055 * and can be expressed via a predefined set of request types.
Amith Yamasanif20d6402014-05-24 15:34:37 -070056 * <p>
57 * The RestrictionsManager forwards the dynamic requests to the active
Amith Yamasani5470bc12014-07-14 17:38:27 -070058 * Restrictions Provider. The Restrictions Provider can respond back to requests by calling
Amith Yamasani9c449332014-07-18 15:19:22 -070059 * {@link #notifyPermissionResponse(String, PersistableBundle)}, when
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -070060 * a response is received from the administrator of the device or user.
Amith Yamasanif20d6402014-05-24 15:34:37 -070061 * The response is relayed back to the application via a protected broadcast,
62 * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
63 * <p>
64 * Static restrictions are specified by an XML file referenced by a meta-data attribute
65 * in the manifest. This enables applications as well as any web administration consoles
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -070066 * to be able to read the list of available restrictions from the apk.
Amith Yamasanif20d6402014-05-24 15:34:37 -070067 * <p>
68 * The syntax of the XML format is as follows:
69 * <pre>
Amith Yamasanic8c84252014-07-13 17:12:12 -070070 * &lt;?xml version="1.0" encoding="utf-8"?&gt;
71 * &lt;restrictions xmlns:android="http://schemas.android.com/apk/res/android" &gt;
Amith Yamasanif20d6402014-05-24 15:34:37 -070072 * &lt;restriction
Amith Yamasanic8c84252014-07-13 17:12:12 -070073 * android:key="string"
74 * android:title="string resource"
75 * android:restrictionType=["bool" | "string" | "integer"
Fyodor Kupolov262f9952015-03-23 18:55:11 -070076 * | "choice" | "multi-select" | "hidden"
77 * | "bundle" | "bundle_array"]
Amith Yamasanic8c84252014-07-13 17:12:12 -070078 * android:description="string resource"
79 * android:entries="string-array resource"
80 * android:entryValues="string-array resource"
Fyodor Kupolov262f9952015-03-23 18:55:11 -070081 * android:defaultValue="reference" &gt;
82 * &lt;restriction ... /&gt;
83 * ...
84 * &lt;/restriction&gt;
Amith Yamasanif20d6402014-05-24 15:34:37 -070085 * &lt;restriction ... /&gt;
Amith Yamasanic8c84252014-07-13 17:12:12 -070086 * ...
Amith Yamasanif20d6402014-05-24 15:34:37 -070087 * &lt;/restrictions&gt;
88 * </pre>
89 * <p>
90 * The attributes for each restriction depend on the restriction type.
Amith Yamasanic8c84252014-07-13 17:12:12 -070091 * <p>
92 * <ul>
93 * <li><code>key</code>, <code>title</code> and <code>restrictionType</code> are mandatory.</li>
94 * <li><code>entries</code> and <code>entryValues</code> are required if <code>restrictionType
95 * </code> is <code>choice</code> or <code>multi-select</code>.</li>
96 * <li><code>defaultValue</code> is optional and its type depends on the
97 * <code>restrictionType</code></li>
98 * <li><code>hidden</code> type must have a <code>defaultValue</code> and will
99 * not be shown to the administrator. It can be used to pass along data that cannot be modified,
100 * such as a version code.</li>
101 * <li><code>description</code> is meant to describe the restriction in more detail to the
102 * administrator controlling the values, if the title is not sufficient.</li>
103 * </ul>
104 * <p>
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700105 * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested
106 * restriction elements.
107 * <p>
Amith Yamasanic8c84252014-07-13 17:12:12 -0700108 * In your manifest's <code>application</code> section, add the meta-data tag to point to
109 * the restrictions XML file as shown below:
110 * <pre>
111 * &lt;application ... &gt;
112 * &lt;meta-data android:name="android.content.APP_RESTRICTIONS"
113 * android:resource="@xml/app_restrictions" /&gt;
114 * ...
115 * &lt;/application&gt;
116 * </pre>
Amith Yamasanif20d6402014-05-24 15:34:37 -0700117 *
118 * @see RestrictionEntry
Amith Yamasanid1d7c022014-08-19 17:03:41 -0700119 * @see RestrictionsReceiver
Amith Yamasanic8c84252014-07-13 17:12:12 -0700120 * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
121 * @see DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)
Amith Yamasanif20d6402014-05-24 15:34:37 -0700122 */
123public class RestrictionsManager {
124
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700125 private static final String TAG = "RestrictionsManager";
126
Amith Yamasanif20d6402014-05-24 15:34:37 -0700127 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700128 * Broadcast intent delivered when a response is received for a permission request. The
129 * application should not interrupt the user by coming to the foreground if it isn't
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700130 * currently in the foreground. It can either post a notification informing
131 * the user of the response or wait until the next time the user launches the app.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700132 * <p>
133 * For instance, if the user requested permission to make an in-app purchase,
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700134 * the app can post a notification that the request had been approved or denied.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700135 * <p>
136 * The broadcast Intent carries the following extra:
137 * {@link #EXTRA_RESPONSE_BUNDLE}.
138 */
139 public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
Amith Yamasani9c449332014-07-18 15:19:22 -0700140 "android.content.action.PERMISSION_RESPONSE_RECEIVED";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700141
142 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700143 * Broadcast intent sent to the Restrictions Provider to handle a permission request from
144 * an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME},
Amith Yamasani9c449332014-07-18 15:19:22 -0700145 * {@link #EXTRA_REQUEST_TYPE}, {@link #EXTRA_REQUEST_ID} and {@link #EXTRA_REQUEST_BUNDLE}.
146 * The Restrictions Provider will handle the request and respond back to the
147 * RestrictionsManager, when a response is available, by calling
148 * {@link #notifyPermissionResponse}.
Amith Yamasani5470bc12014-07-14 17:38:27 -0700149 * <p>
150 * The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN}
151 * permission to ensure that only the system can send the broadcast.
152 */
153 public static final String ACTION_REQUEST_PERMISSION =
154 "android.content.action.REQUEST_PERMISSION";
155
156 /**
Amith Yamasani9c449332014-07-18 15:19:22 -0700157 * Activity intent that is optionally implemented by the Restrictions Provider package
158 * to challenge for an administrator PIN or password locally on the device. Apps will
159 * call this intent using {@link Activity#startActivityForResult}. On a successful
160 * response, {@link Activity#onActivityResult} will return a resultCode of
161 * {@link Activity#RESULT_OK}.
162 * <p>
163 * The intent must contain {@link #EXTRA_REQUEST_BUNDLE} as an extra and the bundle must
164 * contain at least {@link #REQUEST_KEY_MESSAGE} for the activity to display.
165 * <p>
Amith Yamasani51a0e5b2014-09-05 10:51:13 -0700166 * @see #createLocalApprovalIntent()
Amith Yamasani9c449332014-07-18 15:19:22 -0700167 */
168 public static final String ACTION_REQUEST_LOCAL_APPROVAL =
169 "android.content.action.REQUEST_LOCAL_APPROVAL";
170
171 /**
Amith Yamasanif20d6402014-05-24 15:34:37 -0700172 * The package name of the application making the request.
Amith Yamasani9c449332014-07-18 15:19:22 -0700173 * <p>
174 * Type: String
Amith Yamasanif20d6402014-05-24 15:34:37 -0700175 */
Amith Yamasani5470bc12014-07-14 17:38:27 -0700176 public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
177
178 /**
179 * The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
Amith Yamasani9c449332014-07-18 15:19:22 -0700180 * <p>
181 * Type: String
Amith Yamasani5470bc12014-07-14 17:38:27 -0700182 */
183 public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
184
185 /**
Amith Yamasani9c449332014-07-18 15:19:22 -0700186 * The request ID passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
187 * <p>
188 * Type: String
189 */
190 public static final String EXTRA_REQUEST_ID = "android.content.extra.REQUEST_ID";
191
192 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700193 * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
Amith Yamasani9c449332014-07-18 15:19:22 -0700194 * <p>
195 * Type: {@link PersistableBundle}
Amith Yamasani5470bc12014-07-14 17:38:27 -0700196 */
197 public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700198
199 /**
Amith Yamasanif20d6402014-05-24 15:34:37 -0700200 * Contains a response from the administrator for specific request.
201 * The bundle contains the following information, at least:
202 * <ul>
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700203 * <li>{@link #REQUEST_KEY_ID}: The request ID.</li>
204 * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li>
Amith Yamasanif20d6402014-05-24 15:34:37 -0700205 * </ul>
Amith Yamasani9c449332014-07-18 15:19:22 -0700206 * <p>
207 * Type: {@link PersistableBundle}
Amith Yamasanif20d6402014-05-24 15:34:37 -0700208 */
Amith Yamasani5470bc12014-07-14 17:38:27 -0700209 public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700210
211 /**
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700212 * Request type for a simple question, with a possible title and icon.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700213 * <p>
Amith Yamasani9c449332014-07-18 15:19:22 -0700214 * Required keys are: {@link #REQUEST_KEY_MESSAGE}
Amith Yamasanif20d6402014-05-24 15:34:37 -0700215 * <p>
216 * Optional keys are
217 * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
218 * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
219 */
Amith Yamasani5470bc12014-07-14 17:38:27 -0700220 public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700221
222 /**
Amith Yamasanif20d6402014-05-24 15:34:37 -0700223 * Key for request ID contained in the request bundle.
224 * <p>
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700225 * App-generated request ID to identify the specific request when receiving
Amith Yamasanif20d6402014-05-24 15:34:37 -0700226 * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
227 * <p>
228 * Type: String
229 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700230 public static final String REQUEST_KEY_ID = "android.request.id";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700231
232 /**
233 * Key for request data contained in the request bundle.
234 * <p>
235 * Optional, typically used to identify the specific data that is being referred to,
236 * such as the unique identifier for a movie or book. This is not used for display
237 * purposes and is more like a cookie. This value is returned in the
238 * {@link #EXTRA_RESPONSE_BUNDLE}.
239 * <p>
240 * Type: String
241 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700242 public static final String REQUEST_KEY_DATA = "android.request.data";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700243
244 /**
245 * Key for request title contained in the request bundle.
246 * <p>
247 * Optional, typically used as the title of any notification or dialog presented
248 * to the administrator who approves the request.
249 * <p>
250 * Type: String
251 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700252 public static final String REQUEST_KEY_TITLE = "android.request.title";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700253
254 /**
255 * Key for request message contained in the request bundle.
256 * <p>
257 * Required, shown as the actual message in a notification or dialog presented
258 * to the administrator who approves the request.
259 * <p>
260 * Type: String
261 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700262 public static final String REQUEST_KEY_MESSAGE = "android.request.mesg";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700263
264 /**
265 * Key for request icon contained in the request bundle.
266 * <p>
267 * Optional, shown alongside the request message presented to the administrator
Amith Yamasani9c449332014-07-18 15:19:22 -0700268 * who approves the request. The content must be a compressed image such as a
269 * PNG or JPEG, as a byte array.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700270 * <p>
Amith Yamasani9c449332014-07-18 15:19:22 -0700271 * Type: byte[]
Amith Yamasanif20d6402014-05-24 15:34:37 -0700272 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700273 public static final String REQUEST_KEY_ICON = "android.request.icon";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700274
275 /**
276 * Key for request approval button label contained in the request bundle.
277 * <p>
278 * Optional, may be shown as a label on the positive button in a dialog or
279 * notification presented to the administrator who approves the request.
280 * <p>
281 * Type: String
282 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700283 public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700284
285 /**
286 * Key for request rejection button label contained in the request bundle.
287 * <p>
288 * Optional, may be shown as a label on the negative button in a dialog or
289 * notification presented to the administrator who approves the request.
290 * <p>
291 * Type: String
292 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700293 public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700294
295 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700296 * Key for issuing a new request, contained in the request bundle. If this is set to true,
297 * the Restrictions Provider must make a new request. If it is false or not specified, then
298 * the Restrictions Provider can return a cached response that has the same requestId, if
299 * available. If there's no cached response, it will issue a new one to the administrator.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700300 * <p>
Amith Yamasani5470bc12014-07-14 17:38:27 -0700301 * Type: boolean
Amith Yamasanif20d6402014-05-24 15:34:37 -0700302 */
Amith Yamasani5470bc12014-07-14 17:38:27 -0700303 public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700304
305 /**
Amith Yamasanic8c84252014-07-13 17:12:12 -0700306 * Key for the response result in the response bundle sent to the application, for a permission
307 * request. It indicates the status of the request. In some cases an additional message might
308 * be available in {@link #RESPONSE_KEY_MESSAGE}, to be displayed to the user.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700309 * <p>
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700310 * Type: int
311 * <p>
312 * Possible values: {@link #RESULT_APPROVED}, {@link #RESULT_DENIED},
313 * {@link #RESULT_NO_RESPONSE}, {@link #RESULT_UNKNOWN_REQUEST} or
314 * {@link #RESULT_ERROR}.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700315 */
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700316 public static final String RESPONSE_KEY_RESULT = "android.response.result";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700317
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700318 /**
319 * Response result value indicating that the request was approved.
320 */
321 public static final int RESULT_APPROVED = 1;
322
323 /**
324 * Response result value indicating that the request was denied.
325 */
326 public static final int RESULT_DENIED = 2;
327
328 /**
329 * Response result value indicating that the request has not received a response yet.
330 */
331 public static final int RESULT_NO_RESPONSE = 3;
332
333 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700334 * Response result value indicating that the request is unknown, when it's not a new
335 * request.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700336 */
337 public static final int RESULT_UNKNOWN_REQUEST = 4;
338
339 /**
340 * Response result value indicating an error condition. Additional error code might be available
341 * in the response bundle, for the key {@link #RESPONSE_KEY_ERROR_CODE}. There might also be
342 * an associated error message in the response bundle, for the key
Amith Yamasanic8c84252014-07-13 17:12:12 -0700343 * {@link #RESPONSE_KEY_MESSAGE}.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700344 */
345 public static final int RESULT_ERROR = 5;
346
347 /**
348 * Error code indicating that there was a problem with the request.
349 * <p>
350 * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
351 */
352 public static final int RESULT_ERROR_BAD_REQUEST = 1;
353
354 /**
355 * Error code indicating that there was a problem with the network.
356 * <p>
357 * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
358 */
359 public static final int RESULT_ERROR_NETWORK = 2;
360
361 /**
362 * Error code indicating that there was an internal error.
363 * <p>
364 * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle.
365 */
366 public static final int RESULT_ERROR_INTERNAL = 3;
367
368 /**
369 * Key for the optional error code in the response bundle sent to the application.
370 * <p>
371 * Type: int
372 * <p>
373 * Possible values: {@link #RESULT_ERROR_BAD_REQUEST}, {@link #RESULT_ERROR_NETWORK} or
374 * {@link #RESULT_ERROR_INTERNAL}.
375 */
376 public static final String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
377
378 /**
Amith Yamasanic8c84252014-07-13 17:12:12 -0700379 * Key for the optional message in the response bundle sent to the application.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700380 * <p>
381 * Type: String
382 */
Amith Yamasanic8c84252014-07-13 17:12:12 -0700383 public static final String RESPONSE_KEY_MESSAGE = "android.response.msg";
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700384
385 /**
386 * Key for the optional timestamp of when the administrator responded to the permission
387 * request. It is an represented in milliseconds since January 1, 1970 00:00:00.0 UTC.
388 * <p>
389 * Type: long
390 */
391 public static final String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
Amith Yamasanif20d6402014-05-24 15:34:37 -0700392
Amith Yamasanic8c84252014-07-13 17:12:12 -0700393 /**
394 * Name of the meta-data entry in the manifest that points to the XML file containing the
395 * application's available restrictions.
396 * @see #getManifestRestrictions(String)
397 */
398 public static final String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS";
399
400 private static final String TAG_RESTRICTION = "restriction";
401
Amith Yamasanif20d6402014-05-24 15:34:37 -0700402 private final Context mContext;
403 private final IRestrictionsManager mService;
404
405 /**
406 * @hide
407 */
408 public RestrictionsManager(Context context, IRestrictionsManager service) {
409 mContext = context;
410 mService = service;
411 }
412
413 /**
414 * Returns any available set of application-specific restrictions applicable
415 * to this application.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700416 * @return the application restrictions as a Bundle. Returns null if there
417 * are no restrictions.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700418 */
419 public Bundle getApplicationRestrictions() {
420 try {
421 if (mService != null) {
422 return mService.getApplicationRestrictions(mContext.getPackageName());
423 }
424 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700425 throw re.rethrowFromSystemServer();
Amith Yamasanif20d6402014-05-24 15:34:37 -0700426 }
427 return null;
428 }
429
430 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700431 * Called by an application to check if there is an active Restrictions Provider. If
Amith Yamasani9c449332014-07-18 15:19:22 -0700432 * there isn't, {@link #requestPermission(String, String, PersistableBundle)} is not available.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700433 *
Amith Yamasani5470bc12014-07-14 17:38:27 -0700434 * @return whether there is an active Restrictions Provider.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700435 */
436 public boolean hasRestrictionsProvider() {
437 try {
438 if (mService != null) {
439 return mService.hasRestrictionsProvider();
440 }
441 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700442 throw re.rethrowFromSystemServer();
Amith Yamasanif20d6402014-05-24 15:34:37 -0700443 }
444 return false;
445 }
446
447 /**
448 * Called by an application to request permission for an operation. The contents of the
449 * request are passed in a Bundle that contains several pieces of data depending on the
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700450 * chosen request type.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700451 *
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700452 * @param requestType The type of request. The type could be one of the
453 * predefined types specified here or a custom type that the specific
Amith Yamasani5470bc12014-07-14 17:38:27 -0700454 * Restrictions Provider might understand. For custom types, the type name should be
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700455 * namespaced to avoid collisions with predefined types and types specified by
Amith Yamasani5470bc12014-07-14 17:38:27 -0700456 * other Restrictions Providers.
Amith Yamasani9c449332014-07-18 15:19:22 -0700457 * @param requestId A unique id generated by the app that contains sufficient information
458 * to identify the parameters of the request when it receives the id in the response.
459 * @param request A PersistableBundle containing the data corresponding to the specified request
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700460 * type. The keys for the data in the bundle depend on the request type.
Amith Yamasani5470bc12014-07-14 17:38:27 -0700461 *
462 * @throws IllegalArgumentException if any of the required parameters are missing.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700463 */
Amith Yamasani9c449332014-07-18 15:19:22 -0700464 public void requestPermission(String requestType, String requestId, PersistableBundle request) {
Amith Yamasani5470bc12014-07-14 17:38:27 -0700465 if (requestType == null) {
466 throw new NullPointerException("requestType cannot be null");
467 }
Amith Yamasani9c449332014-07-18 15:19:22 -0700468 if (requestId == null) {
469 throw new NullPointerException("requestId cannot be null");
470 }
Amith Yamasani5470bc12014-07-14 17:38:27 -0700471 if (request == null) {
472 throw new NullPointerException("request cannot be null");
473 }
Amith Yamasanif20d6402014-05-24 15:34:37 -0700474 try {
475 if (mService != null) {
Amith Yamasani9c449332014-07-18 15:19:22 -0700476 mService.requestPermission(mContext.getPackageName(), requestType, requestId,
477 request);
Amith Yamasanif20d6402014-05-24 15:34:37 -0700478 }
479 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700480 throw re.rethrowFromSystemServer();
Amith Yamasanif20d6402014-05-24 15:34:37 -0700481 }
482 }
483
Amith Yamasani51a0e5b2014-09-05 10:51:13 -0700484 public Intent createLocalApprovalIntent() {
Amith Yamasani9c449332014-07-18 15:19:22 -0700485 try {
486 if (mService != null) {
Amith Yamasani51a0e5b2014-09-05 10:51:13 -0700487 return mService.createLocalApprovalIntent();
Amith Yamasani9c449332014-07-18 15:19:22 -0700488 }
489 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700490 throw re.rethrowFromSystemServer();
Amith Yamasani9c449332014-07-18 15:19:22 -0700491 }
492 return null;
493 }
494
Amith Yamasanif20d6402014-05-24 15:34:37 -0700495 /**
Amith Yamasani5470bc12014-07-14 17:38:27 -0700496 * Called by the Restrictions Provider to deliver a response to an application.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700497 *
Amith Yamasani5470bc12014-07-14 17:38:27 -0700498 * @param packageName the application to deliver the response to. Cannot be null.
Amith Yamasani9c449332014-07-18 15:19:22 -0700499 * @param response the bundle containing the response status, request ID and other information.
Amith Yamasani5470bc12014-07-14 17:38:27 -0700500 * Cannot be null.
501 *
502 * @throws IllegalArgumentException if any of the required parameters are missing.
Amith Yamasanif20d6402014-05-24 15:34:37 -0700503 */
Amith Yamasani9c449332014-07-18 15:19:22 -0700504 public void notifyPermissionResponse(String packageName, PersistableBundle response) {
Amith Yamasani5470bc12014-07-14 17:38:27 -0700505 if (packageName == null) {
506 throw new NullPointerException("packageName cannot be null");
507 }
508 if (response == null) {
509 throw new NullPointerException("request cannot be null");
510 }
511 if (!response.containsKey(REQUEST_KEY_ID)) {
512 throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
513 }
514 if (!response.containsKey(RESPONSE_KEY_RESULT)) {
515 throw new IllegalArgumentException("RESPONSE_KEY_RESULT must be specified");
516 }
Amith Yamasanif20d6402014-05-24 15:34:37 -0700517 try {
518 if (mService != null) {
519 mService.notifyPermissionResponse(packageName, response);
520 }
521 } catch (RemoteException re) {
Jeff Sharkeyf8880562016-02-26 13:03:01 -0700522 throw re.rethrowFromSystemServer();
Amith Yamasanif20d6402014-05-24 15:34:37 -0700523 }
524 }
525
526 /**
527 * Parse and return the list of restrictions defined in the manifest for the specified
528 * package, if any.
Amith Yamasanif6e2fcc2014-07-10 13:41:55 -0700529 *
Amith Yamasanif20d6402014-05-24 15:34:37 -0700530 * @param packageName The application for which to fetch the restrictions list.
531 * @return The list of RestrictionEntry objects created from the XML file specified
532 * in the manifest, or null if none was specified.
533 */
534 public List<RestrictionEntry> getManifestRestrictions(String packageName) {
Amith Yamasanic8c84252014-07-13 17:12:12 -0700535 ApplicationInfo appInfo = null;
536 try {
537 appInfo = mContext.getPackageManager().getApplicationInfo(packageName,
538 PackageManager.GET_META_DATA);
539 } catch (NameNotFoundException pnfe) {
540 throw new IllegalArgumentException("No such package " + packageName);
541 }
542 if (appInfo == null || !appInfo.metaData.containsKey(META_DATA_APP_RESTRICTIONS)) {
543 return null;
544 }
545
546 XmlResourceParser xml =
547 appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS);
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700548 return loadManifestRestrictions(packageName, xml);
Amith Yamasanic8c84252014-07-13 17:12:12 -0700549 }
550
551 private List<RestrictionEntry> loadManifestRestrictions(String packageName,
552 XmlResourceParser xml) {
553 Context appContext;
554 try {
555 appContext = mContext.createPackageContext(packageName, 0 /* flags */);
556 } catch (NameNotFoundException nnfe) {
557 return null;
558 }
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700559 ArrayList<RestrictionEntry> restrictions = new ArrayList<>();
Amith Yamasanic8c84252014-07-13 17:12:12 -0700560 RestrictionEntry restriction;
561
562 try {
563 int tagType = xml.next();
564 while (tagType != XmlPullParser.END_DOCUMENT) {
565 if (tagType == XmlPullParser.START_TAG) {
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700566 restriction = loadRestrictionElement(appContext, xml);
567 if (restriction != null) {
568 restrictions.add(restriction);
Amith Yamasanic8c84252014-07-13 17:12:12 -0700569 }
570 }
571 tagType = xml.next();
572 }
573 } catch (XmlPullParserException e) {
574 Log.w(TAG, "Reading restriction metadata for " + packageName, e);
575 return null;
576 } catch (IOException e) {
577 Log.w(TAG, "Reading restriction metadata for " + packageName, e);
578 return null;
579 }
580
581 return restrictions;
582 }
583
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700584 private RestrictionEntry loadRestrictionElement(Context appContext, XmlResourceParser xml)
585 throws IOException, XmlPullParserException {
586 if (xml.getName().equals(TAG_RESTRICTION)) {
587 AttributeSet attrSet = Xml.asAttributeSet(xml);
588 if (attrSet != null) {
589 TypedArray a = appContext.obtainStyledAttributes(attrSet,
590 com.android.internal.R.styleable.RestrictionEntry);
591 return loadRestriction(appContext, a, xml);
592 }
593 }
594 return null;
595 }
596
597 private RestrictionEntry loadRestriction(Context appContext, TypedArray a, XmlResourceParser xml)
598 throws IOException, XmlPullParserException {
Amith Yamasanic8c84252014-07-13 17:12:12 -0700599 String key = a.getString(R.styleable.RestrictionEntry_key);
600 int restrictionType = a.getInt(
601 R.styleable.RestrictionEntry_restrictionType, -1);
602 String title = a.getString(R.styleable.RestrictionEntry_title);
603 String description = a.getString(R.styleable.RestrictionEntry_description);
604 int entries = a.getResourceId(R.styleable.RestrictionEntry_entries, 0);
605 int entryValues = a.getResourceId(R.styleable.RestrictionEntry_entryValues, 0);
606
607 if (restrictionType == -1) {
608 Log.w(TAG, "restrictionType cannot be omitted");
609 return null;
610 }
611
612 if (key == null) {
613 Log.w(TAG, "key cannot be omitted");
614 return null;
615 }
616
617 RestrictionEntry restriction = new RestrictionEntry(restrictionType, key);
618 restriction.setTitle(title);
619 restriction.setDescription(description);
620 if (entries != 0) {
621 restriction.setChoiceEntries(appContext, entries);
622 }
623 if (entryValues != 0) {
624 restriction.setChoiceValues(appContext, entryValues);
625 }
626 // Extract the default value based on the type
627 switch (restrictionType) {
628 case RestrictionEntry.TYPE_NULL: // hidden
629 case RestrictionEntry.TYPE_STRING:
630 case RestrictionEntry.TYPE_CHOICE:
631 restriction.setSelectedString(
632 a.getString(R.styleable.RestrictionEntry_defaultValue));
633 break;
634 case RestrictionEntry.TYPE_INTEGER:
635 restriction.setIntValue(
636 a.getInt(R.styleable.RestrictionEntry_defaultValue, 0));
637 break;
638 case RestrictionEntry.TYPE_MULTI_SELECT:
639 int resId = a.getResourceId(R.styleable.RestrictionEntry_defaultValue, 0);
640 if (resId != 0) {
641 restriction.setAllSelectedStrings(
642 appContext.getResources().getStringArray(resId));
643 }
644 break;
645 case RestrictionEntry.TYPE_BOOLEAN:
646 restriction.setSelectedState(
647 a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false));
648 break;
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700649 case RestrictionEntry.TYPE_BUNDLE:
650 case RestrictionEntry.TYPE_BUNDLE_ARRAY:
651 final int outerDepth = xml.getDepth();
652 List<RestrictionEntry> restrictionEntries = new ArrayList<>();
653 while (XmlUtils.nextElementWithin(xml, outerDepth)) {
654 RestrictionEntry childEntry = loadRestrictionElement(appContext, xml);
655 if (childEntry == null) {
656 Log.w(TAG, "Child entry cannot be loaded for bundle restriction " + key);
657 } else {
658 restrictionEntries.add(childEntry);
659 if (restrictionType == RestrictionEntry.TYPE_BUNDLE_ARRAY
660 && childEntry.getType() != RestrictionEntry.TYPE_BUNDLE) {
661 Log.w(TAG, "bundle_array " + key
662 + " can only contain entries of type bundle");
663 }
664 }
665 }
666 restriction.setRestrictions(restrictionEntries.toArray(new RestrictionEntry[
667 restrictionEntries.size()]));
668 break;
Amith Yamasanic8c84252014-07-13 17:12:12 -0700669 default:
670 Log.w(TAG, "Unknown restriction type " + restrictionType);
671 }
672 return restriction;
Amith Yamasanif20d6402014-05-24 15:34:37 -0700673 }
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700674
675 /**
676 * Converts a list of restrictions to the corresponding bundle, using the following mapping:
677 * <table>
678 * <tr><th>RestrictionEntry</th><th>Bundle</th></tr>
679 * <tr><td>{@link RestrictionEntry#TYPE_BOOLEAN}</td><td>{@link Bundle#putBoolean}</td></tr>
Fyodor Kupolov7d35fc382015-08-21 11:25:55 -0700680 * <tr><td>{@link RestrictionEntry#TYPE_CHOICE},
681 * {@link RestrictionEntry#TYPE_MULTI_SELECT}</td>
Fyodor Kupolov262f9952015-03-23 18:55:11 -0700682 * <td>{@link Bundle#putStringArray}</td></tr>
683 * <tr><td>{@link RestrictionEntry#TYPE_INTEGER}</td><td>{@link Bundle#putInt}</td></tr>
684 * <tr><td>{@link RestrictionEntry#TYPE_STRING}</td><td>{@link Bundle#putString}</td></tr>
685 * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE}</td><td>{@link Bundle#putBundle}</td></tr>
686 * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE_ARRAY}</td>
687 * <td>{@link Bundle#putParcelableArray}</td></tr>
688 * </table>
689 * @param entries list of restrictions
690 */
691 public static Bundle convertRestrictionsToBundle(List<RestrictionEntry> entries) {
692 final Bundle bundle = new Bundle();
693 for (RestrictionEntry entry : entries) {
694 addRestrictionToBundle(bundle, entry);
695 }
696 return bundle;
697 }
698
699 private static Bundle addRestrictionToBundle(Bundle bundle, RestrictionEntry entry) {
700 switch (entry.getType()) {
701 case RestrictionEntry.TYPE_BOOLEAN:
702 bundle.putBoolean(entry.getKey(), entry.getSelectedState());
703 break;
704 case RestrictionEntry.TYPE_CHOICE:
705 case RestrictionEntry.TYPE_CHOICE_LEVEL:
706 case RestrictionEntry.TYPE_MULTI_SELECT:
707 bundle.putStringArray(entry.getKey(), entry.getAllSelectedStrings());
708 break;
709 case RestrictionEntry.TYPE_INTEGER:
710 bundle.putInt(entry.getKey(), entry.getIntValue());
711 break;
712 case RestrictionEntry.TYPE_STRING:
713 case RestrictionEntry.TYPE_NULL:
714 bundle.putString(entry.getKey(), entry.getSelectedString());
715 break;
716 case RestrictionEntry.TYPE_BUNDLE:
717 RestrictionEntry[] restrictions = entry.getRestrictions();
718 Bundle childBundle = convertRestrictionsToBundle(Arrays.asList(restrictions));
719 bundle.putBundle(entry.getKey(), childBundle);
720 break;
721 case RestrictionEntry.TYPE_BUNDLE_ARRAY:
722 restrictions = entry.getRestrictions();
723 Bundle[] bundleArray = new Bundle[restrictions.length];
724 for (int i = 0; i < restrictions.length; i++) {
725 bundleArray[i] = addRestrictionToBundle(new Bundle(), restrictions[i]);
726 }
727 bundle.putParcelableArray(entry.getKey(), bundleArray);
728 break;
729 default:
730 throw new IllegalArgumentException(
731 "Unsupported restrictionEntry type: " + entry.getType());
732 }
733 return bundle;
734 }
735
Amith Yamasanif20d6402014-05-24 15:34:37 -0700736}