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