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