diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | api/current.txt | 31 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 9 | ||||
-rw-r--r-- | core/java/android/app/admin/DevicePolicyManager.java | 20 | ||||
-rw-r--r-- | core/java/android/app/admin/IDevicePolicyManager.aidl | 3 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 9 | ||||
-rw-r--r-- | core/java/android/content/IRestrictionsManager.aidl | 30 | ||||
-rw-r--r-- | core/java/android/content/RestrictionEntry.java | 157 | ||||
-rw-r--r-- | core/java/android/content/RestrictionsManager.java | 344 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 6 | ||||
-rw-r--r-- | services/Android.mk | 1 | ||||
-rw-r--r-- | services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java | 41 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 7 | ||||
-rw-r--r-- | services/restrictions/Android.mk | 10 | ||||
-rw-r--r-- | services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java | 171 |
15 files changed, 778 insertions, 62 deletions
diff --git a/Android.mk b/Android.mk index c58ef53fd247..2f3a990e36b8 100644 --- a/Android.mk +++ b/Android.mk @@ -118,6 +118,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/IIntentReceiver.aidl \ core/java/android/content/IIntentSender.aidl \ core/java/android/content/IOnPrimaryClipChangedListener.aidl \ + core/java/android/content/IRestrictionsManager.aidl \ core/java/android/content/ISyncAdapter.aidl \ core/java/android/content/ISyncContext.aidl \ core/java/android/content/ISyncServiceAdapter.aidl \ diff --git a/api/current.txt b/api/current.txt index 0c3702e94d40..856037f3efc7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5229,6 +5229,7 @@ package android.app.admin { method public void setPasswordMinimumUpperCase(android.content.ComponentName, int); method public void setPasswordQuality(android.content.ComponentName, int); method public void setProfileEnabled(android.content.ComponentName); + method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void wipeData(int); @@ -7024,6 +7025,7 @@ package android.content { field public static final java.lang.String NSD_SERVICE = "servicediscovery"; field public static final java.lang.String POWER_SERVICE = "power"; field public static final java.lang.String PRINT_SERVICE = "print"; + field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String STORAGE_SERVICE = "storage"; @@ -7780,12 +7782,14 @@ package android.content { ctor public RestrictionEntry(java.lang.String, java.lang.String); ctor public RestrictionEntry(java.lang.String, boolean); ctor public RestrictionEntry(java.lang.String, java.lang.String[]); + ctor public RestrictionEntry(java.lang.String, int); ctor public RestrictionEntry(android.os.Parcel); method public int describeContents(); method public java.lang.String[] getAllSelectedStrings(); method public java.lang.String[] getChoiceEntries(); method public java.lang.String[] getChoiceValues(); method public java.lang.String getDescription(); + method public int getIntValue(); method public java.lang.String getKey(); method public boolean getSelectedState(); method public java.lang.String getSelectedString(); @@ -7797,6 +7801,7 @@ package android.content { method public void setChoiceValues(java.lang.String[]); method public void setChoiceValues(android.content.Context, int); method public void setDescription(java.lang.String); + method public void setIntValue(int); method public void setSelectedState(boolean); method public void setSelectedString(java.lang.String); method public void setTitle(java.lang.String); @@ -7805,10 +7810,36 @@ package android.content { field public static final android.os.Parcelable.Creator CREATOR; field public static final int TYPE_BOOLEAN = 1; // 0x1 field public static final int TYPE_CHOICE = 2; // 0x2 + field public static final int TYPE_INTEGER = 5; // 0x5 field public static final int TYPE_MULTI_SELECT = 4; // 0x4 field public static final int TYPE_NULL = 0; // 0x0 } + public class RestrictionsManager { + method public android.os.Bundle getApplicationRestrictions(); + method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String); + method public boolean hasRestrictionsProvider(); + method public void notifyPermissionResponse(java.lang.String, android.os.Bundle); + method public void requestPermission(java.lang.String, android.os.Bundle); + field public static final java.lang.String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; + field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.intent.action.REQUEST_PERMISSION"; + field public static final java.lang.String EXTRA_PACKAGE_NAME = "package_name"; + field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "request_bundle"; + field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response_bundle"; + field public static final java.lang.String EXTRA_TEMPLATE_ID = "template_id"; + field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept"; + field public static final java.lang.String REQUEST_KEY_DATA = "android.req_template.data"; + field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.req_template.reject"; + field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.req_template.device"; + field public static final java.lang.String REQUEST_KEY_ICON = "android.req_template.icon"; + field public static final java.lang.String REQUEST_KEY_ID = "android.req_template.req_id"; + field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.req_template.mesg"; + field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor"; + field public static final java.lang.String REQUEST_KEY_TITLE = "android.req_template.title"; + field public static final java.lang.String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple"; + field public static final java.lang.String RESPONSE_KEY_BOOLEAN = "android.req_template.response"; + } + public class SearchRecentSuggestionsProvider extends android.content.ContentProvider { ctor public SearchRecentSuggestionsProvider(); method public int delete(android.net.Uri, java.lang.String, java.lang.String[]); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 460732ff5b9a..4f335bbe18c7 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -35,7 +35,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IntentSender; +import android.content.IRestrictionsManager; import android.content.ReceiverCallNotAllowedException; +import android.content.RestrictionsManager; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; @@ -662,6 +664,13 @@ class ContextImpl extends Context { } }); + registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE); + IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b); + return new RestrictionsManager(ctx, service); + } + }); registerService(PRINT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6b486c409eb4..b3b1d4700d82 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.RestrictionsManager; import android.os.Bundle; import android.os.Handler; import android.os.Process; @@ -2320,4 +2321,23 @@ public class DevicePolicyManager { } } + /** + * Designates a specific broadcast receiver component as the provider for + * making permission requests of a local or remote administrator of the user. + * <p/> + * Only a profile owner can designate the restrictions provider. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param receiver The component name of a BroadcastReceiver that handles the + * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null, + * it removes the restrictions provider previously assigned. + */ + public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) { + if (mService != null) { + try { + mService.setRestrictionsProvider(admin, receiver); + } catch (RemoteException re) { + Log.w(TAG, "Failed to set permission provider on device policy service"); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index cc6d715dae65..7f754dc648ca 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -121,6 +121,9 @@ interface IDevicePolicyManager { void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings); Bundle getApplicationRestrictions(in ComponentName who, in String packageName); + void setRestrictionsProvider(in ComponentName who, in ComponentName provider); + ComponentName getRestrictionsProvider(int userHandle); + void setUserRestriction(in ComponentName who, in String key, boolean enable); void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); void clearCrossProfileIntentFilters(in ComponentName admin); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index d3a979c44998..c69e66947180 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2695,6 +2695,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.content.RestrictionsManager} for retrieving application restrictions + * and requesting permissions for restricted operations. + * @see #getSystemService + * @see android.content.RestrictionsManager + */ + public static final String RESTRICTIONS_SERVICE = "restrictions"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.app.AppOpsManager} for tracking application operations * on the device. * diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl new file mode 100644 index 000000000000..b1c0a3afd9b4 --- /dev/null +++ b/core/java/android/content/IRestrictionsManager.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.Bundle; + +/** + * Interface used by the RestrictionsManager + * @hide + */ +interface IRestrictionsManager { + Bundle getApplicationRestrictions(in String packageName); + boolean hasRestrictionsProvider(); + void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData); + void notifyPermissionResponse(in String packageName, in Bundle response); +} diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 3ff53bf279d6..62f88a9d29b1 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable { */ public static final int TYPE_MULTI_SELECT = 4; + /** + * A type of restriction. Use this for storing an integer value. The range of values + * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. + */ + public static final int TYPE_INTEGER = 5; + /** The type of restriction. */ - private int type; + private int mType; /** The unique key that identifies the restriction. */ - private String key; + private String mKey; /** The user-visible title of the restriction. */ - private String title; + private String mTitle; /** The user-visible secondary description of the restriction. */ - private String description; + private String mDescription; /** The user-visible set of choices used for single-select and multi-select lists. */ - private String [] choices; + private String [] mChoiceEntries; /** The values corresponding to the user-visible choices. The value(s) of this entry will * one or more of these, returned by {@link #getAllSelectedStrings()} and * {@link #getSelectedString()}. */ - private String [] values; + private String [] mChoiceValues; /* The chosen value, whose content depends on the type of the restriction. */ - private String currentValue; + private String mCurrentValue; /* List of selected choices in the multi-select case. */ - private String[] currentValues; + private String[] mCurrentValues; /** * Constructor for {@link #TYPE_CHOICE} type. @@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the current value */ public RestrictionEntry(String key, String selectedString) { - this.key = key; - this.type = TYPE_CHOICE; - this.currentValue = selectedString; + this.mKey = key; + this.mType = TYPE_CHOICE; + this.mCurrentValue = selectedString; } /** @@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable { * @param selectedState whether this restriction is selected or not */ public RestrictionEntry(String key, boolean selectedState) { - this.key = key; - this.type = TYPE_BOOLEAN; + this.mKey = key; + this.mType = TYPE_BOOLEAN; setSelectedState(selectedState); } @@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable { * @param selectedStrings the list of values that are currently selected */ public RestrictionEntry(String key, String[] selectedStrings) { - this.key = key; - this.type = TYPE_MULTI_SELECT; - this.currentValues = selectedStrings; + this.mKey = key; + this.mType = TYPE_MULTI_SELECT; + this.mCurrentValues = selectedStrings; + } + + /** + * Constructor for {@link #TYPE_INTEGER} type. + * @param key the unique key for this restriction + * @param selectedInt the integer value of the restriction + */ + public RestrictionEntry(String key, int selectedInt) { + mKey = key; + mType = TYPE_INTEGER; + setIntValue(selectedInt); } /** @@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable { * @param type the type for this restriction. */ public void setType(int type) { - this.type = type; + this.mType = type; } /** @@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable { * @return the type for this restriction */ public int getType() { - return type; + return mType; } /** @@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable { * single string values. */ public String getSelectedString() { - return currentValue; + return mCurrentValue; } /** @@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable { * null otherwise. */ public String[] getAllSelectedStrings() { - return currentValues; + return mCurrentValues; } /** @@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable { * @return the current selected state of the entry. */ public boolean getSelectedState() { - return Boolean.parseBoolean(currentValue); + return Boolean.parseBoolean(mCurrentValue); + } + + /** + * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}. + * @return the integer value of the entry. + */ + public int getIntValue() { + return Integer.parseInt(mCurrentValue); + } + + /** + * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}. + * @param value the integer value to set. + */ + public void setIntValue(int value) { + mCurrentValue = Integer.toString(value); } /** @@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the string value to select. */ public void setSelectedString(String selectedString) { - currentValue = selectedString; + mCurrentValue = selectedString; } /** @@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable { * @param state the current selected state */ public void setSelectedState(boolean state) { - currentValue = Boolean.toString(state); + mCurrentValue = Boolean.toString(state); } /** @@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable { * @param allSelectedStrings the current list of selected values. */ public void setAllSelectedStrings(String[] allSelectedStrings) { - currentValues = allSelectedStrings; + mCurrentValues = allSelectedStrings; } /** @@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable { * @see #getAllSelectedStrings() */ public void setChoiceValues(String[] choiceValues) { - values = choiceValues; + mChoiceValues = choiceValues; } /** @@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceValues(Context context, int stringArrayResId) { - values = context.getResources().getStringArray(stringArrayResId); + mChoiceValues = context.getResources().getStringArray(stringArrayResId); } /** @@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of possible values. */ public String[] getChoiceValues() { - return values; + return mChoiceValues; } /** @@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceEntries(String[] choiceEntries) { - choices = choiceEntries; + mChoiceEntries = choiceEntries; } /** Sets a list of strings that will be presented as choices to the user. This is similar to @@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable { * @param stringArrayResId the resource id of a string array containing the possible entries. */ public void setChoiceEntries(Context context, int stringArrayResId) { - choices = context.getResources().getStringArray(stringArrayResId); + mChoiceEntries = context.getResources().getStringArray(stringArrayResId); } /** @@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of choices presented to the user. */ public String[] getChoiceEntries() { - return choices; + return mChoiceEntries; } /** @@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible description, null if none was set earlier. */ public String getDescription() { - return description; + return mDescription; } /** @@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable { * @param description the user-visible description string. */ public void setDescription(String description) { - this.description = description; + this.mDescription = description; } /** @@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable { * @return the key for the restriction. */ public String getKey() { - return key; + return mKey; } /** @@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible title for the entry, null if none was set earlier. */ public String getTitle() { - return title; + return mTitle; } /** @@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable { * @param title the user-visible title for the entry. */ public void setTitle(String title) { - this.title = title; + this.mTitle = title; } private boolean equalArrays(String[] one, String[] other) { @@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable { if (!(o instanceof RestrictionEntry)) return false; final RestrictionEntry other = (RestrictionEntry) o; // Make sure that either currentValue matches or currentValues matches. - return type == other.type && key.equals(other.key) + return mType == other.mType && mKey.equals(other.mKey) && - ((currentValues == null && other.currentValues == null - && currentValue != null && currentValue.equals(other.currentValue)) + ((mCurrentValues == null && other.mCurrentValues == null + && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue)) || - (currentValue == null && other.currentValue == null - && currentValues != null && equalArrays(currentValues, other.currentValues))); + (mCurrentValue == null && other.mCurrentValue == null + && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues))); } @Override public int hashCode() { int result = 17; - result = 31 * result + key.hashCode(); - if (currentValue != null) { - result = 31 * result + currentValue.hashCode(); - } else if (currentValues != null) { - for (String value : currentValues) { + result = 31 * result + mKey.hashCode(); + if (mCurrentValue != null) { + result = 31 * result + mCurrentValue.hashCode(); + } else if (mCurrentValues != null) { + for (String value : mCurrentValues) { if (value != null) { result = 31 * result + value.hashCode(); } @@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable { } public RestrictionEntry(Parcel in) { - type = in.readInt(); - key = in.readString(); - title = in.readString(); - description = in.readString(); - choices = readArray(in); - values = readArray(in); - currentValue = in.readString(); - currentValues = readArray(in); + mType = in.readInt(); + mKey = in.readString(); + mTitle = in.readString(); + mDescription = in.readString(); + mChoiceEntries = readArray(in); + mChoiceValues = readArray(in); + mCurrentValue = in.readString(); + mCurrentValues = readArray(in); } @Override @@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(type); - dest.writeString(key); - dest.writeString(title); - dest.writeString(description); - writeArray(dest, choices); - writeArray(dest, values); - dest.writeString(currentValue); - writeArray(dest, currentValues); + dest.writeInt(mType); + dest.writeString(mKey); + dest.writeString(mTitle); + dest.writeString(mDescription); + writeArray(dest, mChoiceEntries); + writeArray(dest, mChoiceValues); + dest.writeString(mCurrentValue); + writeArray(dest, mCurrentValues); } public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() { @@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable { @Override public String toString() { - return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}"; + return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}"; } } diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java new file mode 100644 index 000000000000..0dd0edde12a5 --- /dev/null +++ b/core/java/android/content/RestrictionsManager.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.app.admin.DevicePolicyManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * Provides a mechanism for apps to query restrictions imposed by an entity that + * manages the user. Apps can also send permission requests to a local or remote + * device administrator to override default app-specific restrictions or any other + * operation that needs explicit authorization from the administrator. + * <p> + * Apps can expose a set of restrictions via a runtime receiver mechanism or via + * static meta data in the manifest. + * <p> + * If the user has an active restrictions provider, dynamic requests can be made in + * addition to the statically imposed restrictions. Dynamic requests are app-specific + * and can be expressed via a predefined set of templates. + * <p> + * The RestrictionsManager forwards the dynamic requests to the active + * restrictions provider. The restrictions provider can respond back to requests by calling + * {@link #notifyPermissionResponse(String, Bundle)}, when + * a response is received from the administrator of the device or user + * The response is relayed back to the application via a protected broadcast, + * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}. + * <p> + * Static restrictions are specified by an XML file referenced by a meta-data attribute + * in the manifest. This enables applications as well as any web administration consoles + * to be able to read the template from the apk. + * <p> + * The syntax of the XML format is as follows: + * <pre> + * <restrictions> + * <restriction + * android:key="<key>" + * android:restrictionType="boolean|string|integer|multi-select|null" + * ... /> + * <restriction ... /> + * </restrictions> + * </pre> + * <p> + * The attributes for each restriction depend on the restriction type. + * + * @see RestrictionEntry + */ +public class RestrictionsManager { + + /** + * Broadcast intent delivered when a response is received for a permission + * request. The response is not available for later query, so the receiver + * must persist and/or immediately act upon the response. The application + * should not interrupt the user by coming to the foreground if it isn't + * currently in the foreground. It can post a notification instead, informing + * the user of a change in state. + * <p> + * For instance, if the user requested permission to make an in-app purchase, + * the app can post a notification that the request had been granted or denied, + * and allow the purchase to go through. + * <p> + * The broadcast Intent carries the following extra: + * {@link #EXTRA_RESPONSE_BUNDLE}. + */ + public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = + "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; + + /** + * Protected broadcast intent sent to the active restrictions provider. The intent + * contains the following extras:<p> + * <ul> + * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting + * permission.</li> + * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li> + * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values + * for the request. + * </ul> + * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) + * @see #requestPermission(String, String, Bundle) + */ + public static final String ACTION_REQUEST_PERMISSION = + "android.intent.action.REQUEST_PERMISSION"; + + /** + * The package name of the application making the request. + */ + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + /** + * The template id that specifies what kind of a request it is and may indicate + * how the request is to be presented to the administrator. Must be either one of + * the predefined templates or a custom one specified by the application that the + * restrictions provider is familiar with. + */ + public static final String EXTRA_TEMPLATE_ID = "template_id"; + + /** + * A bundle containing the details about the request. The contents depend on the + * template id. + * @see #EXTRA_TEMPLATE_ID + */ + public static final String EXTRA_REQUEST_BUNDLE = "request_bundle"; + + /** + * Contains a response from the administrator for specific request. + * The bundle contains the following information, at least: + * <ul> + * <li>{@link #REQUEST_KEY_ID}: The request id.</li> + * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li> + * </ul> + * <p> + * And depending on what the request template was, the bundle will contain the actual + * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in + * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator + * approved the request, false otherwise. + */ + public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle"; + + + /** + * Request template that presents a simple question, with a possible title and icon. + * <p> + * Required keys are + * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}. + * <p> + * Optional keys are + * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE}, + * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}. + */ + public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple"; + + /** + * Key for request ID contained in the request bundle. + * <p> + * App-generated request id to identify the specific request when receiving + * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_ID = "android.req_template.req_id"; + + /** + * Key for request data contained in the request bundle. + * <p> + * Optional, typically used to identify the specific data that is being referred to, + * such as the unique identifier for a movie or book. This is not used for display + * purposes and is more like a cookie. This value is returned in the + * {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DATA = "android.req_template.data"; + + /** + * Key for request title contained in the request bundle. + * <p> + * Optional, typically used as the title of any notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_TITLE = "android.req_template.title"; + + /** + * Key for request message contained in the request bundle. + * <p> + * Required, shown as the actual message in a notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg"; + + /** + * Key for request icon contained in the request bundle. + * <p> + * Optional, shown alongside the request message presented to the administrator + * who approves the request. + * <p> + * Type: Bitmap + */ + public static final String REQUEST_KEY_ICON = "android.req_template.icon"; + + /** + * Key for request approval button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the positive button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept"; + + /** + * Key for request rejection button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the negative button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject"; + + /** + * Key for requestor's name contained in the request bundle. This value is not specified by + * the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor"; + + /** + * Key for requestor's device name contained in the request bundle. This value is not specified + * by the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device"; + + /** + * Key for the response in the response bundle sent to the application, for a permission + * request. + * <p> + * Type: boolean + */ + public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response"; + + private static final String TAG = "RestrictionsManager"; + + private final Context mContext; + private final IRestrictionsManager mService; + + /** + * @hide + */ + public RestrictionsManager(Context context, IRestrictionsManager service) { + mContext = context; + mService = service; + } + + /** + * Returns any available set of application-specific restrictions applicable + * to this application. + * @return + */ + public Bundle getApplicationRestrictions() { + try { + if (mService != null) { + return mService.getApplicationRestrictions(mContext.getPackageName()); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return null; + } + + /** + * Called by an application to check if permission requests can be made. If false, + * there is no need to request permission for an operation, unless a static + * restriction applies to that operation. + * @return + */ + public boolean hasRestrictionsProvider() { + try { + if (mService != null) { + return mService.hasRestrictionsProvider(); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return false; + } + + /** + * Called by an application to request permission for an operation. The contents of the + * request are passed in a Bundle that contains several pieces of data depending on the + * chosen request template. + * + * @param requestTemplate The request template to use. The template could be one of the + * predefined templates specified in this class or a custom template that the specific + * Restrictions Provider might understand. For custom templates, the template name should be + * namespaced to avoid collisions with predefined templates and templates specified by + * other Restrictions Provider vendors. + * @param requestData A Bundle containing the data corresponding to the specified request + * template. The keys for the data in the bundle depend on the kind of template chosen. + */ + public void requestPermission(String requestTemplate, Bundle requestData) { + try { + if (mService != null) { + mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Called by the Restrictions Provider when a response is available to be + * delivered to an application. + * @param packageName + * @param response + */ + public void notifyPermissionResponse(String packageName, Bundle response) { + try { + if (mService != null) { + mService.notifyPermissionResponse(packageName, response); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Parse and return the list of restrictions defined in the manifest for the specified + * package, if any. + * @param packageName The application for which to fetch the restrictions list. + * @return The list of RestrictionEntry objects created from the XML file specified + * in the manifest, or null if none was specified. + */ + public List<RestrictionEntry> getManifestRestrictions(String packageName) { + // TODO: + return null; + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 14141d70552e..bb6a1cbc2015 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -258,6 +258,12 @@ <protected-broadcast android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast + android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" /> + <!-- ====================================== --> <!-- Permissions for things that cost money --> <!-- ====================================== --> diff --git a/services/Android.mk b/services/Android.mk index 5fcef64cb963..b4de9038fe6e 100644 --- a/services/Android.mk +++ b/services/Android.mk @@ -25,6 +25,7 @@ services := \ backup \ devicepolicy \ print \ + restrictions \ usb \ voiceinteraction diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d78fb13f4186..a52396e316df 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -130,6 +130,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final boolean DBG = false; + private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; + final Context mContext; final UserManager mUserManager; final PowerManager.WakeLock mWakeLock; @@ -190,6 +192,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // This is the list of component allowed to start lock task mode. final List<ComponentName> mLockTaskComponents = new ArrayList<ComponentName>(); + ComponentName mRestrictionsProvider; + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -944,6 +948,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.startDocument(null, true); out.startTag(null, "policies"); + if (policy.mRestrictionsProvider != null) { + out.attribute(null, ATTR_PERMISSION_PROVIDER, + policy.mRestrictionsProvider.flattenToString()); + } final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { @@ -1039,6 +1047,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new XmlPullParserException( "Settings do not start with policies tag: found " + tag); } + + // Extract the permission provider component name if available + String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER); + if (permissionProvider != null) { + policy.mRestrictionsProvider = ComponentName.unflattenFromString(permissionProvider); + } + type = parser.next(); int outerDepth = parser.getDepth(); policy.mLockTaskComponents.clear(); @@ -3303,6 +3318,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + int userHandle = UserHandle.getCallingUserId(); + DevicePolicyData userData = getUserData(userHandle); + userData.mRestrictionsProvider = permissionProvider; + saveSettingsLocked(userHandle); + } + } + + @Override + public ComponentName getRestrictionsProvider(int userHandle) { + synchronized (this) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can query the permission provider"); + } + DevicePolicyData userData = getUserData(userHandle); + return userData != null ? userData.mRestrictionsProvider : null; + } + } + public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) { int callingUserId = UserHandle.getCallingUserId(); synchronized (this) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c0f60c74296f..27098e7ae08d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -80,6 +80,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; +import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.search.SearchManagerService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; @@ -941,6 +942,12 @@ public final class SystemServer { } try { + mSystemServiceManager.startService(RestrictionsManagerService.class); + } catch (Throwable e) { + reportWtf("starting RestrictionsManagerService", e); + } + + try { mSystemServiceManager.startService(MediaSessionService.class); } catch (Throwable e) { reportWtf("starting MediaSessionService", e); diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk new file mode 100644 index 000000000000..fcf8626af5fd --- /dev/null +++ b/services/restrictions/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := services.restrictions + +LOCAL_SRC_FILES += \ + $(call all-java-files-under,java) + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java new file mode 100644 index 000000000000..e1f77b3a68b1 --- /dev/null +++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.restrictions; + +import android.Manifest; +import android.app.AppGlobals; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.admin.IDevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IRestrictionsManager; +import android.content.RestrictionsManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IUserManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.internal.util.ArrayUtils; +import com.android.server.SystemService; + +/** + * SystemService wrapper for the RestrictionsManager implementation. Publishes the + * Context.RESTRICTIONS_SERVICE. + */ + +public final class RestrictionsManagerService extends SystemService { + private final RestrictionsManagerImpl mRestrictionsManagerImpl; + + public RestrictionsManagerService(Context context) { + super(context); + mRestrictionsManagerImpl = new RestrictionsManagerImpl(context); + } + + @Override + public void onStart() { + publishBinderService(Context.RESTRICTIONS_SERVICE, mRestrictionsManagerImpl); + } + + class RestrictionsManagerImpl extends IRestrictionsManager.Stub { + private final Context mContext; + private final IUserManager mUm; + private final IDevicePolicyManager mDpm; + + public RestrictionsManagerImpl(Context context) { + mContext = context; + mUm = (IUserManager) getBinderService(Context.USER_SERVICE); + mDpm = (IDevicePolicyManager) getBinderService(Context.DEVICE_POLICY_SERVICE); + } + + @Override + public Bundle getApplicationRestrictions(String packageName) throws RemoteException { + return mUm.getApplicationRestrictions(packageName); + } + + @Override + public boolean hasRestrictionsProvider() throws RemoteException { + int userHandle = UserHandle.getCallingUserId(); + if (mDpm != null) { + long ident = Binder.clearCallingIdentity(); + try { + return mDpm.getRestrictionsProvider(userHandle) != null; + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + return false; + } + } + + @Override + public void requestPermission(String packageName, String requestTemplate, + Bundle requestData) throws RemoteException { + int callingUid = Binder.getCallingUid(); + int userHandle = UserHandle.getUserId(callingUid); + if (mDpm != null) { + long ident = Binder.clearCallingIdentity(); + try { + ComponentName restrictionsProvider = + mDpm.getRestrictionsProvider(userHandle); + // Check if there is a restrictions provider + if (restrictionsProvider == null) { + throw new IllegalStateException( + "Cannot request permission without a restrictions provider registered"); + } + // Check that the packageName matches the caller. + enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" + + " match caller "); + // Prepare and broadcast the intent to the provider + Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION); + intent.setComponent(restrictionsProvider); + intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(RestrictionsManager.EXTRA_TEMPLATE_ID, requestTemplate); + intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData); + mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + private void enforceCallerMatchesPackage(int callingUid, String packageName, + String message) { + try { + String[] pkgs = AppGlobals.getPackageManager().getPackagesForUid(callingUid); + if (pkgs != null) { + if (!ArrayUtils.contains(pkgs, packageName)) { + throw new SecurityException(message + callingUid); + } + } + } catch (RemoteException re) { + // Shouldn't happen + } + } + + @Override + public void notifyPermissionResponse(String packageName, Bundle response) + throws RemoteException { + // Check caller + int callingUid = Binder.getCallingUid(); + int userHandle = UserHandle.getUserId(callingUid); + if (mDpm != null) { + long ident = Binder.clearCallingIdentity(); + try { + ComponentName permProvider = mDpm.getRestrictionsProvider(userHandle); + if (permProvider == null) { + throw new SecurityException("No restrictions provider registered for user"); + } + enforceCallerMatchesPackage(callingUid, permProvider.getPackageName(), + "Restrictions provider does not match caller "); + + // Post the response to target package + Intent responseIntent = new Intent( + RestrictionsManager.ACTION_PERMISSION_RESPONSE_RECEIVED); + responseIntent.setPackage(packageName); + responseIntent.putExtra(RestrictionsManager.EXTRA_RESPONSE_BUNDLE, response); + mContext.sendBroadcastAsUser(responseIntent, new UserHandle(userHandle)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } +} |