Application restrictions API
Adds the ability for apps to export some restrictions. The restrictions
are presented in Settings based on the restriction type. The user's
selections are stored by UserManagerService and provided to the
target user's application as a list of RestrictionEntry objects which
contain the key, value(s).
Also introduce a manifest entry for system apps to request that the
app be automatically installed in all users, so that they cannot be
deselected by the owner user.
Shared account filtering for non-whitelisted apps.
Change-Id: I15b741e3c0f3448883cb364c130783f1f6ea7ce6
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f8b7a0c..313260f 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -152,6 +152,9 @@
public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
public static final int ERROR_CODE_BAD_REQUEST = 8;
+ /** @hide */
+ public static final int ERROR_CODE_USER_RESTRICTED = 100;
+
/**
* Bundle key used for the {@link String} account name in results
* from methods which return information about a particular account.
@@ -1526,7 +1529,7 @@
}
public void onError(int code, String message) {
- if (code == ERROR_CODE_CANCELED) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) {
// the authenticator indicated that this request was canceled, do so now
cancel(true /* mayInterruptIfRunning */);
return;
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 132388e..7b07438 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -17,14 +17,17 @@
package android.app;
import java.util.ArrayList;
+import java.util.List;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.RestrictionEntry;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.UserManager;
/**
* Base class for those who need to maintain global application state. You can
@@ -131,6 +134,11 @@
}
}
+ public List<RestrictionEntry> getApplicationRestrictions() {
+ return ((UserManager) getSystemService(USER_SERVICE))
+ .getApplicationRestrictions(getPackageName(), android.os.Process.myUserHandle());
+ }
+
public void registerComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.add(callback);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8a9eed2..7dd76cd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -45,6 +45,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
/**
* Interface to global information about an application environment. This is
@@ -286,6 +287,15 @@
public abstract Context getApplicationContext();
/**
+ * Returns the list of restrictions for the application, or null if there are no
+ * restrictions.
+ * @return
+ */
+ public List<RestrictionEntry> getApplicationRestrictions() {
+ return getApplicationContext().getApplicationRestrictions();
+ }
+
+ /**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
* methods of activities and other components are called. Note that you
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 53c47d2..e1461e3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2415,6 +2415,15 @@
"android.intent.action.PRE_BOOT_COMPLETED";
/**
+ * Broadcast to a specific application to query any supported restrictions to impose
+ * on restricted users. The response should contain an extra {@link #EXTRA_RESTRICTIONS}
+ * which is of type <code>ArrayList<RestrictionEntry></code>.
+ * @see RestrictionEntry
+ */
+ public static final String ACTION_GET_RESTRICTION_ENTRIES =
+ "android.intent.action.GET_RESTRICTION_ENTRIES";
+
+ /**
* Sent the first time a user is starting, to allow system apps to
* perform one time initialization. (This will not be seen by third
* party applications because a newly initialized user does not have any
@@ -3146,6 +3155,12 @@
public static final String EXTRA_USER_HANDLE =
"android.intent.extra.user_handle";
+ /**
+ * Extra used in the response from a BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}.
+ */
+ public static final String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/RestrictionEntry.aidl b/core/java/android/content/RestrictionEntry.aidl
new file mode 100644
index 0000000..b93eee3
--- /dev/null
+++ b/core/java/android/content/RestrictionEntry.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2013, 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;
+
+parcelable RestrictionEntry;
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
new file mode 100644
index 0000000..196460c
--- /dev/null
+++ b/core/java/android/content/RestrictionEntry.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2013 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.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Inherited;
+
+/**
+ * Applications can expose restrictions for a restricted user on a
+ * multiuser device. The administrator can configure these restrictions that will then be
+ * applied to the restricted user. Each RestrictionsEntry is one configurable restriction.
+ * <p/>
+ * Any application that chooses to expose such restrictions does so by implementing a
+ * receiver that handles the {@link Intent.ACTION_GET_RESTRICTION_ENTRIES} action.
+ * The receiver then returns a result bundle that contains an entry called "restrictions", whose
+ * value is an ArrayList<RestrictionsEntry>.
+ */
+public class RestrictionEntry implements Parcelable {
+
+ /**
+ * A type of restriction. Use this one for information that needs to be transferred across
+ * but shouldn't be presented to the user in the UI.
+ */
+ public static final int TYPE_NULL = 0;
+ /**
+ * A type of restriction. Use this for storing true/false values, typically presented as
+ * a checkbox in the UI.
+ */
+ public static final int TYPE_BOOLEAN = 1;
+ /**
+ * A type of restriction. Use this for storing a string value, typically presented as
+ * a single-select list. The {@link #values} and {@link #choices} need to have the list of
+ * possible values and the corresponding localized strings, respectively, to present in the UI.
+ */
+ public static final int TYPE_CHOICE = 2;
+ /**
+ * A type of restriction. Use this for storing a string value, typically presented as
+ * a single-select list. The {@link #values} and {@link #choices} need to have the list of
+ * possible values and the corresponding localized strings, respectively, to present in the UI.
+ * The presentation could imply that values in lower array indices are included when a
+ * particular value is chosen.
+ */
+ public static final int TYPE_CHOICE_LEVEL = 3;
+ /**
+ * A type of restriction. Use this for presenting a multi-select list where more than one
+ * entry can be selected, such as for choosing specific titles to white-list.
+ * The {@link #values} and {@link #choices} need to have the list of
+ * possible values and the corresponding localized strings, respectively, to present in the UI.
+ * Use {@link #getMultipleValues()} and {@link #setMultipleValues(String[])} to manipulate
+ * the selections.
+ */
+ public static final int TYPE_MULTI_SELECT = 4;
+
+ /** The type of restriction. */
+ public int type;
+
+ /** The unique key that identifies the restriction. */
+ public String key;
+
+ /** The user-visible title of the restriction. */
+ public String title;
+
+ /** The user-visible secondary description of the restriction. */
+ public String description;
+
+ /** The user-visible set of choices used for single-select and multi-select lists. */
+ public String [] choices;
+
+ /** The values corresponding to the user-visible choices. The value(s) of this entry will
+ * one or more of these, returned by {@link #getMultipleValues()} and
+ * {@link #getStringValue()}.
+ */
+ public String [] values;
+
+ /* The chosen value, whose content depends on the type of the restriction. */
+ private String currentValue;
+ /* List of selected choices in the multi-select case. */
+ private String[] currentValues;
+
+ /**
+ * Constructor for {@link #TYPE_CHOICE} and {@link #TYPE_CHOICE_LEVEL} types.
+ * @param key the unique key for this restriction
+ * @param value the current value
+ */
+ public RestrictionEntry(String key, String value) {
+ this.key = key;
+ this.currentValue = value;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_BOOLEAN} type.
+ * @param key the unique key for this restriction
+ * @param value the current value
+ */
+ public RestrictionEntry(String key, boolean value) {
+ this.key = key;
+ setValue(value);
+ }
+
+ /**
+ * Constructor for {@link #TYPE_MULTI_SELECT} type.
+ * @param key the unique key for this restriction
+ * @param multipleValues the list of values that are currently selected
+ */
+ public RestrictionEntry(String key, String[] multipleValues) {
+ this.key = key;
+ this.currentValues = multipleValues;
+ }
+
+ /**
+ * Returns the current value. Null for {@link #TYPE_MULTI_SELECT} type.
+ * @return the current value
+ */
+ public String getStringValue() {
+ return currentValue;
+ }
+
+ /**
+ * Returns the list of current selections. Null if the type is not {@link #TYPE_MULTI_SELECT}.
+ * @return the list of current selections.
+ */
+ public String[] getMultipleValues() {
+ return currentValues;
+ }
+
+ /**
+ * Returns the current boolean value for entries of type {@link #TYPE_BOOLEAN}.
+ * @return the current value
+ */
+ public boolean getBooleanValue() {
+ return Boolean.parseBoolean(currentValue);
+ }
+
+ /**
+ * Set the current string value.
+ * @param s the current value
+ */
+ public void setValue(String s) {
+ currentValue = s;
+ }
+
+ /**
+ * Sets the current boolean value.
+ * @param b the current value
+ */
+ public void setValue(boolean b) {
+ currentValue = Boolean.toString(b);
+ }
+
+ /**
+ * Sets the current list of selected values.
+ * @param values the current list of selected values
+ */
+ public void setMultipleValues(String[] values) {
+ currentValues = values;
+ }
+
+ private boolean equalArrays(String[] one, String[] other) {
+ if (one.length != other.length) return false;
+ for (int i = 0; i < one.length; i++) {
+ if (!one[i].equals(other[i])) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ 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)
+ &&
+ ((currentValues == null && other.currentValues == null
+ && currentValue != null && currentValue.equals(other.currentValue))
+ ||
+ (currentValue == null && other.currentValue == null
+ && currentValues != null && equalArrays(currentValues, other.currentValues)));
+ }
+
+ @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) {
+ if (value != null) {
+ result = 31 * result + value.hashCode();
+ }
+ }
+ }
+ return result;
+ }
+
+ private String[] readArray(Parcel in) {
+ int count = in.readInt();
+ String[] values = new String[count];
+ for (int i = 0; i < count; i++) {
+ values[i] = in.readString();
+ }
+ return values;
+ }
+
+ 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);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void writeArray(Parcel dest, String[] values) {
+ if (values == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(values.length);
+ for (int i = 0; i < values.length; i++) {
+ dest.writeString(values[i]);
+ }
+ }
+ }
+
+ @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);
+ }
+
+ public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
+ public RestrictionEntry createFromParcel(Parcel source) {
+ return new RestrictionEntry(source);
+ }
+
+ public RestrictionEntry[] newArray(int size) {
+ return new RestrictionEntry[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+ }
+}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 85f7aa5..77ca7f6 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -217,7 +217,10 @@
* @hide
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
-
+
+ /** @hide */
+ public boolean requiredForAllUsers;
+
public PackageInfo() {
}
@@ -258,6 +261,7 @@
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
dest.writeInt(installLocation);
+ dest.writeInt(requiredForAllUsers ? 1 : 0);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -296,5 +300,6 @@
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
installLocation = source.readInt();
+ requiredForAllUsers = source.readInt() != 0;
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5eac903..149b8e5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -289,6 +289,7 @@
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
+ pi.requiredForAllUsers = p.mRequiredForAllUsers;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -1760,6 +1761,11 @@
false)) {
ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers,
+ false)) {
+ owner.mRequiredForAllUsers = true;
+ }
}
if (sa.getBoolean(
@@ -3271,6 +3277,9 @@
public int installLocation;
+ /* An app that's required for all users and cannot be uninstalled for a user */
+ public boolean mRequiredForAllUsers;
+
/**
* Digest suitable for comparing whether this package's manifest is the
* same as another.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 34c9740..4c2d7a6 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.pm.UserInfo;
+import android.content.RestrictionEntry;
import android.graphics.Bitmap;
/**
@@ -40,4 +41,7 @@
int getUserHandle(int userSerialNumber);
Bundle getUserRestrictions(int userHandle);
void setUserRestrictions(in Bundle restrictions, int userHandle);
+ void setApplicationRestrictions(in String packageName, in List<RestrictionEntry> entries,
+ int userHandle);
+ List<RestrictionEntry> getApplicationRestrictions(in String packageName, int userHandle);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 51e3e7c..7c05528 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -17,6 +17,7 @@
import android.app.ActivityManagerNative;
import android.content.Context;
+import android.content.RestrictionEntry;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -126,7 +127,19 @@
public boolean isUserAGoat() {
return false;
}
-
+
+ /**
+ * @hide
+ */
+ public boolean isUserRestricted() {
+ try {
+ return mService.getUserInfo(getUserHandle()).isRestricted();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not check if user restricted ", re);
+ return false;
+ }
+ }
+
/**
* Return whether the given user is actively running. This means that
* the user is in the "started" state, not "stopped" -- it is currently
@@ -450,10 +463,34 @@
}
/**
- * Returns whether the current user is allow to toggle location sharing settings.
+ * Returns whether the current user is allowed to toggle location sharing settings.
* @hide
*/
public boolean isLocationSharingToggleAllowed() {
return getUserRestrictions().getBoolean(ALLOW_CONFIG_LOCATION_ACCESS);
}
+
+ /**
+ * @hide
+ */
+ public List<RestrictionEntry> getApplicationRestrictions(String packageName, UserHandle user) {
+ try {
+ return mService.getApplicationRestrictions(packageName, user.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get application restrictions for user " + user.getIdentifier());
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+ UserHandle user) {
+ try {
+ mService.setApplicationRestrictions(packageName, entries, user.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set application restrictions for user " + user.getIdentifier());
+ }
+ }
}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f1d8c03..6f59817 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -254,7 +254,11 @@
not normally be used by applications; it requires that the system keep
your application running at all times. -->
<attr name="persistent" format="boolean" />
-
+
+ <!-- Flag to specify if this application needs to be present for all users. Only pre-installed
+ applications can request this feature. Default value is false. -->
+ <attr name="requiredForAllUsers" format="boolean" />
+
<!-- Flag indicating whether the application can be debugged, even when
running on a device that is running in user mode. -->
<attr name="debuggable" format="boolean" />
@@ -844,6 +848,7 @@
for normal behavior. -->
<attr name="hasCode" format="boolean" />
<attr name="persistent" />
+ <attr name="requiredForAllUsers" />
<!-- Specify whether the components in this application are enabled or not (that is, can be
instantiated by the system).
If "false", it overrides any component specific values (a value of "true" will not
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6a8407f..7a6a1e9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1041,4 +1041,6 @@
<string name="config_chooseTypeAndAccountActivity"
>android/android.accounts.ChooseTypeAndAccountActivity</string>
+ <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
+ <string name="config_appsAuthorizedForSharedAccounts"></string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 42e5cf1..ef0ae03 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2042,6 +2042,7 @@
<eat-comment />
<public type="attr" name="windowOverscan" />
+ <public type="attr" name="requiredForAllUsers" />
<public type="style" name="Theme.NoTitleBar.Overscan" />
<public type="style" name="Theme.Light.NoTitleBar.Overscan" />
<public type="style" name="Theme.Black.NoTitleBar.Overscan" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 614ac07..a763c90 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -865,6 +865,7 @@
<java-symbol type="string" name="owner_name" />
<java-symbol type="string" name="config_chooseAccountActivity" />
<java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+ <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="plurals" name="abbrev_in_num_days" />