RestrictionsManager.java revision 9c44933958bc03b93eac2452b26dd75567581b86
1/* 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 19import android.app.Activity; 20import android.app.admin.DevicePolicyManager; 21import 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; 26import android.os.Bundle; 27import android.os.PersistableBundle; 28import android.os.RemoteException; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.util.Xml; 32 33import com.android.internal.R; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37 38import java.io.IOException; 39import java.util.ArrayList; 40import java.util.List; 41 42/** 43 * Provides a mechanism for apps to query restrictions imposed by an entity that 44 * manages the user. Apps can also send permission requests to a local or remote 45 * device administrator to override default app-specific restrictions or any other 46 * operation that needs explicit authorization from the administrator. 47 * <p> 48 * Apps can expose a set of restrictions via an XML file specified in the manifest. 49 * <p> 50 * If the user has an active Restrictions Provider, dynamic requests can be made in 51 * addition to the statically imposed restrictions. Dynamic requests are app-specific 52 * and can be expressed via a predefined set of request types. 53 * <p> 54 * The RestrictionsManager forwards the dynamic requests to the active 55 * Restrictions Provider. The Restrictions Provider can respond back to requests by calling 56 * {@link #notifyPermissionResponse(String, PersistableBundle)}, when 57 * a response is received from the administrator of the device or user. 58 * The response is relayed back to the application via a protected broadcast, 59 * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}. 60 * <p> 61 * Static restrictions are specified by an XML file referenced by a meta-data attribute 62 * in the manifest. This enables applications as well as any web administration consoles 63 * to be able to read the list of available restrictions from the apk. 64 * <p> 65 * The syntax of the XML format is as follows: 66 * <pre> 67 * <?xml version="1.0" encoding="utf-8"?> 68 * <restrictions xmlns:android="http://schemas.android.com/apk/res/android" > 69 * <restriction 70 * android:key="string" 71 * android:title="string resource" 72 * android:restrictionType=["bool" | "string" | "integer" 73 * | "choice" | "multi-select" | "hidden"] 74 * android:description="string resource" 75 * android:entries="string-array resource" 76 * android:entryValues="string-array resource" 77 * android:defaultValue="reference" 78 * /> 79 * <restriction ... /> 80 * ... 81 * </restrictions> 82 * </pre> 83 * <p> 84 * The attributes for each restriction depend on the restriction type. 85 * <p> 86 * <ul> 87 * <li><code>key</code>, <code>title</code> and <code>restrictionType</code> are mandatory.</li> 88 * <li><code>entries</code> and <code>entryValues</code> are required if <code>restrictionType 89 * </code> is <code>choice</code> or <code>multi-select</code>.</li> 90 * <li><code>defaultValue</code> is optional and its type depends on the 91 * <code>restrictionType</code></li> 92 * <li><code>hidden</code> type must have a <code>defaultValue</code> and will 93 * not be shown to the administrator. It can be used to pass along data that cannot be modified, 94 * such as a version code.</li> 95 * <li><code>description</code> is meant to describe the restriction in more detail to the 96 * administrator controlling the values, if the title is not sufficient.</li> 97 * </ul> 98 * <p> 99 * In your manifest's <code>application</code> section, add the meta-data tag to point to 100 * the restrictions XML file as shown below: 101 * <pre> 102 * <application ... > 103 * <meta-data android:name="android.content.APP_RESTRICTIONS" 104 * android:resource="@xml/app_restrictions" /> 105 * ... 106 * </application> 107 * </pre> 108 * 109 * @see RestrictionEntry 110 * @see AbstractRestrictionsProvider 111 * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) 112 * @see DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle) 113 */ 114public class RestrictionsManager { 115 116 private static final String TAG = "RestrictionsManager"; 117 118 /** 119 * Broadcast intent delivered when a response is received for a permission request. The 120 * application should not interrupt the user by coming to the foreground if it isn't 121 * currently in the foreground. It can either post a notification informing 122 * the user of the response or wait until the next time the user launches the app. 123 * <p> 124 * For instance, if the user requested permission to make an in-app purchase, 125 * the app can post a notification that the request had been approved or denied. 126 * <p> 127 * The broadcast Intent carries the following extra: 128 * {@link #EXTRA_RESPONSE_BUNDLE}. 129 */ 130 public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = 131 "android.content.action.PERMISSION_RESPONSE_RECEIVED"; 132 133 /** 134 * Broadcast intent sent to the Restrictions Provider to handle a permission request from 135 * an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME}, 136 * {@link #EXTRA_REQUEST_TYPE}, {@link #EXTRA_REQUEST_ID} and {@link #EXTRA_REQUEST_BUNDLE}. 137 * The Restrictions Provider will handle the request and respond back to the 138 * RestrictionsManager, when a response is available, by calling 139 * {@link #notifyPermissionResponse}. 140 * <p> 141 * The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN} 142 * permission to ensure that only the system can send the broadcast. 143 */ 144 public static final String ACTION_REQUEST_PERMISSION = 145 "android.content.action.REQUEST_PERMISSION"; 146 147 /** 148 * Activity intent that is optionally implemented by the Restrictions Provider package 149 * to challenge for an administrator PIN or password locally on the device. Apps will 150 * call this intent using {@link Activity#startActivityForResult}. On a successful 151 * response, {@link Activity#onActivityResult} will return a resultCode of 152 * {@link Activity#RESULT_OK}. 153 * <p> 154 * The intent must contain {@link #EXTRA_REQUEST_BUNDLE} as an extra and the bundle must 155 * contain at least {@link #REQUEST_KEY_MESSAGE} for the activity to display. 156 * <p> 157 * @see #getLocalApprovalIntent() 158 */ 159 public static final String ACTION_REQUEST_LOCAL_APPROVAL = 160 "android.content.action.REQUEST_LOCAL_APPROVAL"; 161 162 /** 163 * The package name of the application making the request. 164 * <p> 165 * Type: String 166 */ 167 public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; 168 169 /** 170 * The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. 171 * <p> 172 * Type: String 173 */ 174 public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE"; 175 176 /** 177 * The request ID passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. 178 * <p> 179 * Type: String 180 */ 181 public static final String EXTRA_REQUEST_ID = "android.content.extra.REQUEST_ID"; 182 183 /** 184 * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. 185 * <p> 186 * Type: {@link PersistableBundle} 187 */ 188 public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE"; 189 190 /** 191 * Contains a response from the administrator for specific request. 192 * The bundle contains the following information, at least: 193 * <ul> 194 * <li>{@link #REQUEST_KEY_ID}: The request ID.</li> 195 * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li> 196 * </ul> 197 * <p> 198 * Type: {@link PersistableBundle} 199 */ 200 public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE"; 201 202 /** 203 * Request type for a simple question, with a possible title and icon. 204 * <p> 205 * Required keys are: {@link #REQUEST_KEY_MESSAGE} 206 * <p> 207 * Optional keys are 208 * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE}, 209 * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}. 210 */ 211 public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval"; 212 213 /** 214 * Key for request ID contained in the request bundle. 215 * <p> 216 * App-generated request ID to identify the specific request when receiving 217 * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. 218 * <p> 219 * Type: String 220 */ 221 public static final String REQUEST_KEY_ID = "android.request.id"; 222 223 /** 224 * Key for request data contained in the request bundle. 225 * <p> 226 * Optional, typically used to identify the specific data that is being referred to, 227 * such as the unique identifier for a movie or book. This is not used for display 228 * purposes and is more like a cookie. This value is returned in the 229 * {@link #EXTRA_RESPONSE_BUNDLE}. 230 * <p> 231 * Type: String 232 */ 233 public static final String REQUEST_KEY_DATA = "android.request.data"; 234 235 /** 236 * Key for request title contained in the request bundle. 237 * <p> 238 * Optional, typically used as the title of any notification or dialog presented 239 * to the administrator who approves the request. 240 * <p> 241 * Type: String 242 */ 243 public static final String REQUEST_KEY_TITLE = "android.request.title"; 244 245 /** 246 * Key for request message contained in the request bundle. 247 * <p> 248 * Required, shown as the actual message in a notification or dialog presented 249 * to the administrator who approves the request. 250 * <p> 251 * Type: String 252 */ 253 public static final String REQUEST_KEY_MESSAGE = "android.request.mesg"; 254 255 /** 256 * Key for request icon contained in the request bundle. 257 * <p> 258 * Optional, shown alongside the request message presented to the administrator 259 * who approves the request. The content must be a compressed image such as a 260 * PNG or JPEG, as a byte array. 261 * <p> 262 * Type: byte[] 263 */ 264 public static final String REQUEST_KEY_ICON = "android.request.icon"; 265 266 /** 267 * Key for request approval button label contained in the request bundle. 268 * <p> 269 * Optional, may be shown as a label on the positive button in a dialog or 270 * notification presented to the administrator who approves the request. 271 * <p> 272 * Type: String 273 */ 274 public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label"; 275 276 /** 277 * Key for request rejection button label contained in the request bundle. 278 * <p> 279 * Optional, may be shown as a label on the negative button in a dialog or 280 * notification presented to the administrator who approves the request. 281 * <p> 282 * Type: String 283 */ 284 public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label"; 285 286 /** 287 * Key for issuing a new request, contained in the request bundle. If this is set to true, 288 * the Restrictions Provider must make a new request. If it is false or not specified, then 289 * the Restrictions Provider can return a cached response that has the same requestId, if 290 * available. If there's no cached response, it will issue a new one to the administrator. 291 * <p> 292 * Type: boolean 293 */ 294 public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request"; 295 296 /** 297 * Key for the response result in the response bundle sent to the application, for a permission 298 * request. It indicates the status of the request. In some cases an additional message might 299 * be available in {@link #RESPONSE_KEY_MESSAGE}, to be displayed to the user. 300 * <p> 301 * Type: int 302 * <p> 303 * Possible values: {@link #RESULT_APPROVED}, {@link #RESULT_DENIED}, 304 * {@link #RESULT_NO_RESPONSE}, {@link #RESULT_UNKNOWN_REQUEST} or 305 * {@link #RESULT_ERROR}. 306 */ 307 public static final String RESPONSE_KEY_RESULT = "android.response.result"; 308 309 /** 310 * Response result value indicating that the request was approved. 311 */ 312 public static final int RESULT_APPROVED = 1; 313 314 /** 315 * Response result value indicating that the request was denied. 316 */ 317 public static final int RESULT_DENIED = 2; 318 319 /** 320 * Response result value indicating that the request has not received a response yet. 321 */ 322 public static final int RESULT_NO_RESPONSE = 3; 323 324 /** 325 * Response result value indicating that the request is unknown, when it's not a new 326 * request. 327 */ 328 public static final int RESULT_UNKNOWN_REQUEST = 4; 329 330 /** 331 * Response result value indicating an error condition. Additional error code might be available 332 * in the response bundle, for the key {@link #RESPONSE_KEY_ERROR_CODE}. There might also be 333 * an associated error message in the response bundle, for the key 334 * {@link #RESPONSE_KEY_MESSAGE}. 335 */ 336 public static final int RESULT_ERROR = 5; 337 338 /** 339 * Error code indicating that there was a problem with the request. 340 * <p> 341 * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle. 342 */ 343 public static final int RESULT_ERROR_BAD_REQUEST = 1; 344 345 /** 346 * Error code indicating that there was a problem with the network. 347 * <p> 348 * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle. 349 */ 350 public static final int RESULT_ERROR_NETWORK = 2; 351 352 /** 353 * Error code indicating that there was an internal error. 354 * <p> 355 * Stored in {@link #RESPONSE_KEY_ERROR_CODE} field in the response bundle. 356 */ 357 public static final int RESULT_ERROR_INTERNAL = 3; 358 359 /** 360 * Key for the optional error code in the response bundle sent to the application. 361 * <p> 362 * Type: int 363 * <p> 364 * Possible values: {@link #RESULT_ERROR_BAD_REQUEST}, {@link #RESULT_ERROR_NETWORK} or 365 * {@link #RESULT_ERROR_INTERNAL}. 366 */ 367 public static final String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode"; 368 369 /** 370 * Key for the optional message in the response bundle sent to the application. 371 * <p> 372 * Type: String 373 */ 374 public static final String RESPONSE_KEY_MESSAGE = "android.response.msg"; 375 376 /** 377 * Key for the optional timestamp of when the administrator responded to the permission 378 * request. It is an represented in milliseconds since January 1, 1970 00:00:00.0 UTC. 379 * <p> 380 * Type: long 381 */ 382 public static final String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp"; 383 384 /** 385 * Name of the meta-data entry in the manifest that points to the XML file containing the 386 * application's available restrictions. 387 * @see #getManifestRestrictions(String) 388 */ 389 public static final String META_DATA_APP_RESTRICTIONS = "android.content.APP_RESTRICTIONS"; 390 391 private static final String TAG_RESTRICTION = "restriction"; 392 393 private final Context mContext; 394 private final IRestrictionsManager mService; 395 396 /** 397 * @hide 398 */ 399 public RestrictionsManager(Context context, IRestrictionsManager service) { 400 mContext = context; 401 mService = service; 402 } 403 404 /** 405 * Returns any available set of application-specific restrictions applicable 406 * to this application. 407 * @return the application restrictions as a Bundle. Returns null if there 408 * are no restrictions. 409 */ 410 public Bundle getApplicationRestrictions() { 411 try { 412 if (mService != null) { 413 return mService.getApplicationRestrictions(mContext.getPackageName()); 414 } 415 } catch (RemoteException re) { 416 Log.w(TAG, "Couldn't reach service"); 417 } 418 return null; 419 } 420 421 /** 422 * Called by an application to check if there is an active Restrictions Provider. If 423 * there isn't, {@link #requestPermission(String, String, PersistableBundle)} is not available. 424 * 425 * @return whether there is an active Restrictions Provider. 426 */ 427 public boolean hasRestrictionsProvider() { 428 try { 429 if (mService != null) { 430 return mService.hasRestrictionsProvider(); 431 } 432 } catch (RemoteException re) { 433 Log.w(TAG, "Couldn't reach service"); 434 } 435 return false; 436 } 437 438 /** 439 * Called by an application to request permission for an operation. The contents of the 440 * request are passed in a Bundle that contains several pieces of data depending on the 441 * chosen request type. 442 * 443 * @param requestType The type of request. The type could be one of the 444 * predefined types specified here or a custom type that the specific 445 * Restrictions Provider might understand. For custom types, the type name should be 446 * namespaced to avoid collisions with predefined types and types specified by 447 * other Restrictions Providers. 448 * @param requestId A unique id generated by the app that contains sufficient information 449 * to identify the parameters of the request when it receives the id in the response. 450 * @param request A PersistableBundle containing the data corresponding to the specified request 451 * type. The keys for the data in the bundle depend on the request type. 452 * 453 * @throws IllegalArgumentException if any of the required parameters are missing. 454 */ 455 public void requestPermission(String requestType, String requestId, PersistableBundle request) { 456 if (requestType == null) { 457 throw new NullPointerException("requestType cannot be null"); 458 } 459 if (requestId == null) { 460 throw new NullPointerException("requestId cannot be null"); 461 } 462 if (request == null) { 463 throw new NullPointerException("request cannot be null"); 464 } 465 try { 466 if (mService != null) { 467 mService.requestPermission(mContext.getPackageName(), requestType, requestId, 468 request); 469 } 470 } catch (RemoteException re) { 471 Log.w(TAG, "Couldn't reach service"); 472 } 473 } 474 475 public Intent getLocalApprovalIntent() { 476 try { 477 if (mService != null) { 478 return mService.getLocalApprovalIntent(); 479 } 480 } catch (RemoteException re) { 481 Log.w(TAG, "Couldn't reach service"); 482 } 483 return null; 484 } 485 486 /** 487 * Called by the Restrictions Provider to deliver a response to an application. 488 * 489 * @param packageName the application to deliver the response to. Cannot be null. 490 * @param response the bundle containing the response status, request ID and other information. 491 * Cannot be null. 492 * 493 * @throws IllegalArgumentException if any of the required parameters are missing. 494 */ 495 public void notifyPermissionResponse(String packageName, PersistableBundle response) { 496 if (packageName == null) { 497 throw new NullPointerException("packageName cannot be null"); 498 } 499 if (response == null) { 500 throw new NullPointerException("request cannot be null"); 501 } 502 if (!response.containsKey(REQUEST_KEY_ID)) { 503 throw new IllegalArgumentException("REQUEST_KEY_ID must be specified"); 504 } 505 if (!response.containsKey(RESPONSE_KEY_RESULT)) { 506 throw new IllegalArgumentException("RESPONSE_KEY_RESULT must be specified"); 507 } 508 try { 509 if (mService != null) { 510 mService.notifyPermissionResponse(packageName, response); 511 } 512 } catch (RemoteException re) { 513 Log.w(TAG, "Couldn't reach service"); 514 } 515 } 516 517 /** 518 * Parse and return the list of restrictions defined in the manifest for the specified 519 * package, if any. 520 * 521 * @param packageName The application for which to fetch the restrictions list. 522 * @return The list of RestrictionEntry objects created from the XML file specified 523 * in the manifest, or null if none was specified. 524 */ 525 public List<RestrictionEntry> getManifestRestrictions(String packageName) { 526 ApplicationInfo appInfo = null; 527 try { 528 appInfo = mContext.getPackageManager().getApplicationInfo(packageName, 529 PackageManager.GET_META_DATA); 530 } catch (NameNotFoundException pnfe) { 531 throw new IllegalArgumentException("No such package " + packageName); 532 } 533 if (appInfo == null || !appInfo.metaData.containsKey(META_DATA_APP_RESTRICTIONS)) { 534 return null; 535 } 536 537 XmlResourceParser xml = 538 appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS); 539 List<RestrictionEntry> restrictions = loadManifestRestrictions(packageName, xml); 540 541 return restrictions; 542 } 543 544 private List<RestrictionEntry> loadManifestRestrictions(String packageName, 545 XmlResourceParser xml) { 546 Context appContext; 547 try { 548 appContext = mContext.createPackageContext(packageName, 0 /* flags */); 549 } catch (NameNotFoundException nnfe) { 550 return null; 551 } 552 ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>(); 553 RestrictionEntry restriction; 554 555 try { 556 int tagType = xml.next(); 557 while (tagType != XmlPullParser.END_DOCUMENT) { 558 if (tagType == XmlPullParser.START_TAG) { 559 if (xml.getName().equals(TAG_RESTRICTION)) { 560 AttributeSet attrSet = Xml.asAttributeSet(xml); 561 if (attrSet != null) { 562 TypedArray a = appContext.obtainStyledAttributes(attrSet, 563 com.android.internal.R.styleable.RestrictionEntry); 564 restriction = loadRestriction(appContext, a); 565 if (restriction != null) { 566 restrictions.add(restriction); 567 } 568 } 569 } 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 584 private RestrictionEntry loadRestriction(Context appContext, TypedArray a) { 585 String key = a.getString(R.styleable.RestrictionEntry_key); 586 int restrictionType = a.getInt( 587 R.styleable.RestrictionEntry_restrictionType, -1); 588 String title = a.getString(R.styleable.RestrictionEntry_title); 589 String description = a.getString(R.styleable.RestrictionEntry_description); 590 int entries = a.getResourceId(R.styleable.RestrictionEntry_entries, 0); 591 int entryValues = a.getResourceId(R.styleable.RestrictionEntry_entryValues, 0); 592 593 if (restrictionType == -1) { 594 Log.w(TAG, "restrictionType cannot be omitted"); 595 return null; 596 } 597 598 if (key == null) { 599 Log.w(TAG, "key cannot be omitted"); 600 return null; 601 } 602 603 RestrictionEntry restriction = new RestrictionEntry(restrictionType, key); 604 restriction.setTitle(title); 605 restriction.setDescription(description); 606 if (entries != 0) { 607 restriction.setChoiceEntries(appContext, entries); 608 } 609 if (entryValues != 0) { 610 restriction.setChoiceValues(appContext, entryValues); 611 } 612 // Extract the default value based on the type 613 switch (restrictionType) { 614 case RestrictionEntry.TYPE_NULL: // hidden 615 case RestrictionEntry.TYPE_STRING: 616 case RestrictionEntry.TYPE_CHOICE: 617 restriction.setSelectedString( 618 a.getString(R.styleable.RestrictionEntry_defaultValue)); 619 break; 620 case RestrictionEntry.TYPE_INTEGER: 621 restriction.setIntValue( 622 a.getInt(R.styleable.RestrictionEntry_defaultValue, 0)); 623 break; 624 case RestrictionEntry.TYPE_MULTI_SELECT: 625 int resId = a.getResourceId(R.styleable.RestrictionEntry_defaultValue, 0); 626 if (resId != 0) { 627 restriction.setAllSelectedStrings( 628 appContext.getResources().getStringArray(resId)); 629 } 630 break; 631 case RestrictionEntry.TYPE_BOOLEAN: 632 restriction.setSelectedState( 633 a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false)); 634 break; 635 default: 636 Log.w(TAG, "Unknown restriction type " + restrictionType); 637 } 638 return restriction; 639 } 640} 641