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