Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 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 | |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 19 | import android.app.Activity; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 20 | import android.app.admin.DevicePolicyManager; |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 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; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 26 | import android.os.Bundle; |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 27 | import android.os.PersistableBundle; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 28 | import android.os.RemoteException; |
Amith Yamasani | d1d7c02 | 2014-08-19 17:03:41 -0700 | [diff] [blame] | 29 | import android.service.restrictions.RestrictionsReceiver; |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 30 | import android.util.AttributeSet; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 31 | import android.util.Log; |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 32 | import android.util.Xml; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 33 | |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 34 | import com.android.internal.R; |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 35 | import com.android.internal.util.XmlUtils; |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 36 | |
| 37 | import org.xmlpull.v1.XmlPullParser; |
| 38 | import org.xmlpull.v1.XmlPullParserException; |
| 39 | |
| 40 | import java.io.IOException; |
| 41 | import java.util.ArrayList; |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 42 | import java.util.Arrays; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 43 | import 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 51 | * Apps can expose a set of restrictions via an XML file specified in the manifest. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 52 | * <p> |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 53 | * If the user has an active Restrictions Provider, dynamic requests can be made in |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 54 | * addition to the statically imposed restrictions. Dynamic requests are app-specific |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 55 | * and can be expressed via a predefined set of request types. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 56 | * <p> |
| 57 | * The RestrictionsManager forwards the dynamic requests to the active |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 58 | * Restrictions Provider. The Restrictions Provider can respond back to requests by calling |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 59 | * {@link #notifyPermissionResponse(String, PersistableBundle)}, when |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 60 | * a response is received from the administrator of the device or user. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 61 | * 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 66 | * to be able to read the list of available restrictions from the apk. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 67 | * <p> |
| 68 | * The syntax of the XML format is as follows: |
| 69 | * <pre> |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 70 | * <?xml version="1.0" encoding="utf-8"?> |
| 71 | * <restrictions xmlns:android="http://schemas.android.com/apk/res/android" > |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 72 | * <restriction |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 73 | * android:key="string" |
| 74 | * android:title="string resource" |
| 75 | * android:restrictionType=["bool" | "string" | "integer" |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 76 | * | "choice" | "multi-select" | "hidden" |
| 77 | * | "bundle" | "bundle_array"] |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 78 | * android:description="string resource" |
| 79 | * android:entries="string-array resource" |
| 80 | * android:entryValues="string-array resource" |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 81 | * android:defaultValue="reference" > |
| 82 | * <restriction ... /> |
| 83 | * ... |
| 84 | * </restriction> |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 85 | * <restriction ... /> |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 86 | * ... |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 87 | * </restrictions> |
| 88 | * </pre> |
| 89 | * <p> |
| 90 | * The attributes for each restriction depend on the restriction type. |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 91 | * <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 Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 105 | * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested |
| 106 | * restriction elements. |
| 107 | * <p> |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 108 | * 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 | * <application ... > |
| 112 | * <meta-data android:name="android.content.APP_RESTRICTIONS" |
| 113 | * android:resource="@xml/app_restrictions" /> |
| 114 | * ... |
| 115 | * </application> |
| 116 | * </pre> |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 117 | * |
| 118 | * @see RestrictionEntry |
Amith Yamasani | d1d7c02 | 2014-08-19 17:03:41 -0700 | [diff] [blame] | 119 | * @see RestrictionsReceiver |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 120 | * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) |
| 121 | * @see DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle) |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 122 | */ |
| 123 | public class RestrictionsManager { |
| 124 | |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 125 | private static final String TAG = "RestrictionsManager"; |
| 126 | |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 127 | /** |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 128 | * 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 130 | * 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 132 | * <p> |
| 133 | * For instance, if the user requested permission to make an in-app purchase, |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 134 | * the app can post a notification that the request had been approved or denied. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 135 | * <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 Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 140 | "android.content.action.PERMISSION_RESPONSE_RECEIVED"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 141 | |
| 142 | /** |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 143 | * 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 Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 145 | * {@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 Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 149 | * <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 Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 157 | * 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 Yamasani | 51a0e5b | 2014-09-05 10:51:13 -0700 | [diff] [blame] | 166 | * @see #createLocalApprovalIntent() |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 167 | */ |
| 168 | public static final String ACTION_REQUEST_LOCAL_APPROVAL = |
| 169 | "android.content.action.REQUEST_LOCAL_APPROVAL"; |
| 170 | |
| 171 | /** |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 172 | * The package name of the application making the request. |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 173 | * <p> |
| 174 | * Type: String |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 175 | */ |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 176 | 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 Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 180 | * <p> |
| 181 | * Type: String |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 182 | */ |
| 183 | public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE"; |
| 184 | |
| 185 | /** |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 186 | * 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 Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 193 | * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast. |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 194 | * <p> |
| 195 | * Type: {@link PersistableBundle} |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 196 | */ |
| 197 | public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 198 | |
| 199 | /** |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 200 | * Contains a response from the administrator for specific request. |
| 201 | * The bundle contains the following information, at least: |
| 202 | * <ul> |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 203 | * <li>{@link #REQUEST_KEY_ID}: The request ID.</li> |
| 204 | * <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li> |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 205 | * </ul> |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 206 | * <p> |
| 207 | * Type: {@link PersistableBundle} |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 208 | */ |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 209 | public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 210 | |
| 211 | /** |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 212 | * Request type for a simple question, with a possible title and icon. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 213 | * <p> |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 214 | * Required keys are: {@link #REQUEST_KEY_MESSAGE} |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 215 | * <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 Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 220 | public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval"; |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 221 | |
| 222 | /** |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 223 | * Key for request ID contained in the request bundle. |
| 224 | * <p> |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 225 | * App-generated request ID to identify the specific request when receiving |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 226 | * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. |
| 227 | * <p> |
| 228 | * Type: String |
| 229 | */ |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 230 | public static final String REQUEST_KEY_ID = "android.request.id"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 231 | |
| 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 242 | public static final String REQUEST_KEY_DATA = "android.request.data"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 243 | |
| 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 252 | public static final String REQUEST_KEY_TITLE = "android.request.title"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 253 | |
| 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 262 | public static final String REQUEST_KEY_MESSAGE = "android.request.mesg"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 263 | |
| 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 Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 268 | * who approves the request. The content must be a compressed image such as a |
| 269 | * PNG or JPEG, as a byte array. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 270 | * <p> |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 271 | * Type: byte[] |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 272 | */ |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 273 | public static final String REQUEST_KEY_ICON = "android.request.icon"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 274 | |
| 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 283 | public static final String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 284 | |
| 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 293 | public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 294 | |
| 295 | /** |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 296 | * 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 300 | * <p> |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 301 | * Type: boolean |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 302 | */ |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 303 | public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 304 | |
| 305 | /** |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 306 | * 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 309 | * <p> |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 310 | * 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 315 | */ |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 316 | public static final String RESPONSE_KEY_RESULT = "android.response.result"; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 317 | |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 318 | /** |
| 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 Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 334 | * Response result value indicating that the request is unknown, when it's not a new |
| 335 | * request. |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 336 | */ |
| 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 Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 343 | * {@link #RESPONSE_KEY_MESSAGE}. |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 344 | */ |
| 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 Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 379 | * Key for the optional message in the response bundle sent to the application. |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 380 | * <p> |
| 381 | * Type: String |
| 382 | */ |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 383 | public static final String RESPONSE_KEY_MESSAGE = "android.response.msg"; |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 384 | |
| 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 392 | |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 393 | /** |
| 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 402 | 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 416 | * @return the application restrictions as a Bundle. Returns null if there |
| 417 | * are no restrictions. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 418 | */ |
| 419 | public Bundle getApplicationRestrictions() { |
| 420 | try { |
| 421 | if (mService != null) { |
| 422 | return mService.getApplicationRestrictions(mContext.getPackageName()); |
| 423 | } |
| 424 | } catch (RemoteException re) { |
Jeff Sharkey | f888056 | 2016-02-26 13:03:01 -0700 | [diff] [blame] | 425 | throw re.rethrowFromSystemServer(); |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 426 | } |
| 427 | return null; |
| 428 | } |
| 429 | |
| 430 | /** |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 431 | * Called by an application to check if there is an active Restrictions Provider. If |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 432 | * there isn't, {@link #requestPermission(String, String, PersistableBundle)} is not available. |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 433 | * |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 434 | * @return whether there is an active Restrictions Provider. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 435 | */ |
| 436 | public boolean hasRestrictionsProvider() { |
| 437 | try { |
| 438 | if (mService != null) { |
| 439 | return mService.hasRestrictionsProvider(); |
| 440 | } |
| 441 | } catch (RemoteException re) { |
Jeff Sharkey | f888056 | 2016-02-26 13:03:01 -0700 | [diff] [blame] | 442 | throw re.rethrowFromSystemServer(); |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 443 | } |
| 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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 450 | * chosen request type. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 451 | * |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 452 | * @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 Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 454 | * Restrictions Provider might understand. For custom types, the type name should be |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 455 | * namespaced to avoid collisions with predefined types and types specified by |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 456 | * other Restrictions Providers. |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 457 | * @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 Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 460 | * type. The keys for the data in the bundle depend on the request type. |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 461 | * |
| 462 | * @throws IllegalArgumentException if any of the required parameters are missing. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 463 | */ |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 464 | public void requestPermission(String requestType, String requestId, PersistableBundle request) { |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 465 | if (requestType == null) { |
| 466 | throw new NullPointerException("requestType cannot be null"); |
| 467 | } |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 468 | if (requestId == null) { |
| 469 | throw new NullPointerException("requestId cannot be null"); |
| 470 | } |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 471 | if (request == null) { |
| 472 | throw new NullPointerException("request cannot be null"); |
| 473 | } |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 474 | try { |
| 475 | if (mService != null) { |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 476 | mService.requestPermission(mContext.getPackageName(), requestType, requestId, |
| 477 | request); |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 478 | } |
| 479 | } catch (RemoteException re) { |
Jeff Sharkey | f888056 | 2016-02-26 13:03:01 -0700 | [diff] [blame] | 480 | throw re.rethrowFromSystemServer(); |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 481 | } |
| 482 | } |
| 483 | |
Amith Yamasani | 51a0e5b | 2014-09-05 10:51:13 -0700 | [diff] [blame] | 484 | public Intent createLocalApprovalIntent() { |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 485 | try { |
| 486 | if (mService != null) { |
Amith Yamasani | 51a0e5b | 2014-09-05 10:51:13 -0700 | [diff] [blame] | 487 | return mService.createLocalApprovalIntent(); |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 488 | } |
| 489 | } catch (RemoteException re) { |
Jeff Sharkey | f888056 | 2016-02-26 13:03:01 -0700 | [diff] [blame] | 490 | throw re.rethrowFromSystemServer(); |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 491 | } |
| 492 | return null; |
| 493 | } |
| 494 | |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 495 | /** |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 496 | * Called by the Restrictions Provider to deliver a response to an application. |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 497 | * |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 498 | * @param packageName the application to deliver the response to. Cannot be null. |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 499 | * @param response the bundle containing the response status, request ID and other information. |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 500 | * Cannot be null. |
| 501 | * |
| 502 | * @throws IllegalArgumentException if any of the required parameters are missing. |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 503 | */ |
Amith Yamasani | 9c44933 | 2014-07-18 15:19:22 -0700 | [diff] [blame] | 504 | public void notifyPermissionResponse(String packageName, PersistableBundle response) { |
Amith Yamasani | 5470bc1 | 2014-07-14 17:38:27 -0700 | [diff] [blame] | 505 | 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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 517 | try { |
| 518 | if (mService != null) { |
| 519 | mService.notifyPermissionResponse(packageName, response); |
| 520 | } |
| 521 | } catch (RemoteException re) { |
Jeff Sharkey | f888056 | 2016-02-26 13:03:01 -0700 | [diff] [blame] | 522 | throw re.rethrowFromSystemServer(); |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 523 | } |
| 524 | } |
| 525 | |
| 526 | /** |
| 527 | * Parse and return the list of restrictions defined in the manifest for the specified |
| 528 | * package, if any. |
Amith Yamasani | f6e2fcc | 2014-07-10 13:41:55 -0700 | [diff] [blame] | 529 | * |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 530 | * @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 Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 535 | 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 Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 548 | return loadManifestRestrictions(packageName, xml); |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 549 | } |
| 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 Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 559 | ArrayList<RestrictionEntry> restrictions = new ArrayList<>(); |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 560 | RestrictionEntry restriction; |
| 561 | |
| 562 | try { |
| 563 | int tagType = xml.next(); |
| 564 | while (tagType != XmlPullParser.END_DOCUMENT) { |
| 565 | if (tagType == XmlPullParser.START_TAG) { |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 566 | restriction = loadRestrictionElement(appContext, xml); |
| 567 | if (restriction != null) { |
| 568 | restrictions.add(restriction); |
Amith Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 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 | |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 584 | 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 Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 599 | 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 Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 649 | 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 Yamasani | c8c8425 | 2014-07-13 17:12:12 -0700 | [diff] [blame] | 669 | default: |
| 670 | Log.w(TAG, "Unknown restriction type " + restrictionType); |
| 671 | } |
| 672 | return restriction; |
Amith Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 673 | } |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 674 | |
| 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 Kupolov | 7d35fc38 | 2015-08-21 11:25:55 -0700 | [diff] [blame] | 680 | * <tr><td>{@link RestrictionEntry#TYPE_CHOICE}, |
| 681 | * {@link RestrictionEntry#TYPE_MULTI_SELECT}</td> |
Fyodor Kupolov | 262f995 | 2015-03-23 18:55:11 -0700 | [diff] [blame] | 682 | * <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 Yamasani | f20d640 | 2014-05-24 15:34:37 -0700 | [diff] [blame] | 736 | } |