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