From df2e92a535e19c00edd37318d974dab992ccc2c1 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Fri, 1 Mar 2013 17:04:38 -0800 Subject: [PATCH] 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 --- api/current.txt | 31 +++ core/java/android/accounts/AccountManager.java | 5 +- core/java/android/app/Application.java | 8 + core/java/android/content/Context.java | 10 + core/java/android/content/Intent.java | 15 ++ core/java/android/content/RestrictionEntry.aidl | 20 ++ core/java/android/content/RestrictionEntry.java | 275 +++++++++++++++++++++ core/java/android/content/pm/PackageInfo.java | 7 +- core/java/android/content/pm/PackageParser.java | 9 + core/java/android/os/IUserManager.aidl | 4 + core/java/android/os/UserManager.java | 41 ++- core/res/res/values/attrs_manifest.xml | 7 +- core/res/res/values/config.xml | 2 + core/res/res/values/public.xml | 1 + core/res/res/values/symbols.xml | 1 + .../server/accounts/AccountManagerService.java | 94 +++++-- .../com/android/server/pm/UserManagerService.java | 154 ++++++++++++ 17 files changed, 659 insertions(+), 25 deletions(-) create mode 100644 core/java/android/content/RestrictionEntry.aidl create mode 100644 core/java/android/content/RestrictionEntry.java diff --git a/api/current.txt b/api/current.txt index 12dc6b4e38e9..f4b4d5320e85 100644 --- a/api/current.txt +++ b/api/current.txt @@ -846,6 +846,7 @@ package android { field public static final int reqNavigation = 16843306; // 0x101022a field public static final int reqTouchScreen = 16843303; // 0x1010227 field public static final int required = 16843406; // 0x101028e + field public static final int requiredForAllUsers = 16843728; // 0x10103d0 field public static final int requiresFadingEdge = 16843685; // 0x10103a5 field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364 field public static final int resizeMode = 16843619; // 0x1010363 @@ -5405,6 +5406,7 @@ package android.content { method public abstract java.lang.String[] fileList(); method public abstract android.content.Context getApplicationContext(); method public abstract android.content.pm.ApplicationInfo getApplicationInfo(); + method public java.util.List getApplicationRestrictions(); method public abstract android.content.res.AssetManager getAssets(); method public abstract java.io.File getCacheDir(); method public abstract java.lang.ClassLoader getClassLoader(); @@ -5848,6 +5850,7 @@ package android.content { field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE"; field public static final java.lang.String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST"; field public static final java.lang.String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT"; + field public static final java.lang.String ACTION_GET_RESTRICTION_ENTRIES = "android.intent.action.GET_RESTRICTION_ENTRIES"; field public static final java.lang.String ACTION_GTALK_SERVICE_CONNECTED = "android.intent.action.GTALK_CONNECTED"; field public static final java.lang.String ACTION_GTALK_SERVICE_DISCONNECTED = "android.intent.action.GTALK_DISCONNECTED"; field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; @@ -5989,6 +5992,7 @@ package android.content { field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER"; field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token"; field public static final java.lang.String EXTRA_REPLACING = "android.intent.extra.REPLACING"; + field public static final java.lang.String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions"; field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT"; field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON"; field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE"; @@ -6231,6 +6235,33 @@ package android.content { ctor public ReceiverCallNotAllowedException(java.lang.String); } + public class RestrictionEntry implements android.os.Parcelable { + 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(android.os.Parcel); + method public int describeContents(); + method public boolean getBooleanValue(); + method public java.lang.String[] getMultipleValues(); + method public java.lang.String getStringValue(); + method public void setMultipleValues(java.lang.String[]); + method public void setValue(java.lang.String); + method public void setValue(boolean); + method public void writeToParcel(android.os.Parcel, int); + 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_CHOICE_LEVEL = 3; // 0x3 + field public static final int TYPE_MULTI_SELECT = 4; // 0x4 + field public static final int TYPE_NULL = 0; // 0x0 + field public java.lang.String[] choices; + field public java.lang.String description; + field public java.lang.String key; + field public java.lang.String title; + field public int type; + field public java.lang.String[] values; + } + 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/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index f8b7a0c949be..313260f65f9f 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -152,6 +152,9 @@ public class AccountManager { 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 class AccountManager { } 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 132388ef3e24..7b074382b447 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 class Application extends ContextWrapper implements ComponentCallbacks2 { } } + public List 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 8a9eed27d4f4..7dd76cd676f0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -45,6 +45,7 @@ import java.io.FileNotFoundException; 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 class Context { public abstract Context getApplicationContext(); /** + * Returns the list of restrictions for the application, or null if there are no + * restrictions. + * @return + */ + public List 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 53c47d2abe9d..e1461e3dde7e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2415,6 +2415,15 @@ public class Intent implements Parcelable, Cloneable { "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 ArrayList<RestrictionEntry>. + * @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 class Intent implements Parcelable, Cloneable { 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 000000000000..b93eee3a5ff7 --- /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 000000000000..196460cbd936 --- /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. + *

+ * 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. + */ +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 CREATOR = new Creator() { + 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 85f7aa5113a2..77ca7f68e75e 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -217,7 +217,10 @@ public class PackageInfo implements Parcelable { * @hide */ public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY; - + + /** @hide */ + public boolean requiredForAllUsers; + public PackageInfo() { } @@ -258,6 +261,7 @@ public class PackageInfo implements Parcelable { dest.writeTypedArray(configPreferences, parcelableFlags); dest.writeTypedArray(reqFeatures, parcelableFlags); dest.writeInt(installLocation); + dest.writeInt(requiredForAllUsers ? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -296,5 +300,6 @@ public class PackageInfo implements Parcelable { 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 5eac90373d17..149b8e5d2a95 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -289,6 +289,7 @@ public class PackageParser { 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 @@ public class PackageParser { 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 class PackageParser { 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 34c9740557a4..4c2d7a643220 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -20,6 +20,7 @@ package android.os; 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 @@ interface IUserManager { int getUserHandle(int userSerialNumber); Bundle getUserRestrictions(int userHandle); void setUserRestrictions(in Bundle restrictions, int userHandle); + void setApplicationRestrictions(in String packageName, in List entries, + int userHandle); + List getApplicationRestrictions(in String packageName, int userHandle); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 51e3e7c93733..7c055283775b 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -17,6 +17,7 @@ package android.os; 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 class UserManager { 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 @@ public class UserManager { } /** - * 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 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 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 f1d8c03c5e58..6f59817bf671 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. --> - + + + + @@ -844,6 +848,7 @@ for normal behavior. --> + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 42e5cf1ba4ba..ef0ae03a371f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2042,6 +2042,7 @@ + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 614ac07165e9..a763c90d54af 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -865,6 +865,7 @@ + diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index 49295f56b3e2..09daf569349b 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -455,7 +455,6 @@ public class AccountManagerService @Override public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) { - Slog.d(TAG, "onServiceChanged() for userId " + userId); validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */); } @@ -588,16 +587,12 @@ public class AccountManagerService if (result != null) { if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { // Create a Session for the target user and pass in the bundle - Slog.i(TAG, "getAccountCredentialsForCloning returned success, " - + "sending result to target user"); completeCloningAccount(result, account, toAccounts); } else { - Slog.e(TAG, "getAccountCredentialsForCloning returned failure"); clonePassword(fromAccounts, toAccounts, account); } return; } else { - Slog.e(TAG, "getAccountCredentialsForCloning returned null"); clonePassword(fromAccounts, toAccounts, account); super.onResult(result); } @@ -645,15 +640,12 @@ public class AccountManagerService if (result != null) { if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { // TODO: Anything? - Slog.i(TAG, "addAccount returned success"); } else { // TODO: Show error notification // TODO: Should we remove the shadow account to avoid retries? - Slog.e(TAG, "addAccountFromCredentials returned failure"); } return; } else { - Slog.e(TAG, "addAccountFromCredentials returned null"); super.onResult(result); } } @@ -1433,6 +1425,17 @@ public class AccountManagerService if (accountType == null) throw new IllegalArgumentException("accountType is null"); checkManageAccountsPermission(); + // Is user allowed to modify accounts? + if (!getUserManager().getUserRestrictions(Binder.getCallingUserHandle()) + .getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) { + try { + response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED, + "User is not allowed to add an account!"); + } catch (RemoteException re) { + } + return; + } + UserAccounts accounts = getUserAccountsForCaller(); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); @@ -1573,17 +1576,19 @@ public class AccountManagerService private volatile Account[] mAccountsOfType = null; private volatile ArrayList mAccountsWithFeatures = null; private volatile int mCurrentAccount = 0; + private int mCallingUid; public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, - IAccountManagerResponse response, String type, String[] features) { + IAccountManagerResponse response, String type, String[] features, int callingUid) { super(accounts, response, type, false /* expectActivityLaunch */, true /* stripAuthTokenFromResult */); + mCallingUid = callingUid; mFeatures = features; } public void run() throws RemoteException { synchronized (mAccounts.cacheLock) { - mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType); + mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid); } // check whether each account matches the requested features mAccountsWithFeatures = new ArrayList(mAccountsOfType.length); @@ -1668,10 +1673,11 @@ public class AccountManagerService public Account[] getAccounts(int userId) { checkReadAccountsPermission(); UserAccounts accounts = getUserAccounts(userId); + int callingUid = Binder.getCallingUid(); long identityToken = clearCallingIdentity(); try { synchronized (accounts.cacheLock) { - return getAccountsFromCacheLocked(accounts, null); + return getAccountsFromCacheLocked(accounts, null, callingUid); } } finally { restoreCallingIdentity(identityToken); @@ -1711,7 +1717,8 @@ public class AccountManagerService UserAccounts userAccounts = getUserAccounts(userId); if (userAccounts == null) continue; synchronized (userAccounts.cacheLock) { - Account[] accounts = getAccountsFromCacheLocked(userAccounts, null); + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null, + Binder.getCallingUid()); for (int a = 0; a < accounts.length; a++) { runningAccounts.add(new AccountAndUser(accounts[a], userId)); } @@ -1725,9 +1732,10 @@ public class AccountManagerService @Override public Account[] getAccountsAsUser(String type, int userId) { + final int callingUid = Binder.getCallingUid(); // Only allow the system process to read accounts of other users if (userId != UserHandle.getCallingUserId() - && Binder.getCallingUid() != android.os.Process.myUid()) { + && callingUid != android.os.Process.myUid()) { throw new SecurityException("User " + UserHandle.getCallingUserId() + " trying to get account for " + userId); } @@ -1742,7 +1750,7 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { synchronized (accounts.cacheLock) { - return getAccountsFromCacheLocked(accounts, type); + return getAccountsFromCacheLocked(accounts, type, callingUid); } } finally { restoreCallingIdentity(identityToken); @@ -1826,19 +1834,21 @@ public class AccountManagerService if (type == null) throw new IllegalArgumentException("accountType is null"); checkReadAccountsPermission(); UserAccounts userAccounts = getUserAccountsForCaller(); + int callingUid = Binder.getCallingUid(); long identityToken = clearCallingIdentity(); try { if (features == null || features.length == 0) { Account[] accounts; synchronized (userAccounts.cacheLock) { - accounts = getAccountsFromCacheLocked(userAccounts, type); + accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid); } Bundle result = new Bundle(); result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); onResult(response, result); return; } - new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind(); + new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features, + callingUid).bind(); } finally { restoreCallingIdentity(identityToken); } @@ -2352,7 +2362,8 @@ public class AccountManagerService } } } else { - Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */); + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */, + android.os.Process.myUid()); fout.println("Accounts: " + accounts.length); for (Account account : accounts) { fout.println(" " + account); @@ -2691,13 +2702,56 @@ public class AccountManagerService accounts.accountCache.put(account.type, newAccountsForType); } - protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) { + private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered, + int callingUid) { + if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0 + || callingUid == android.os.Process.myUid()) { + return unfiltered; + } + if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) { + String[] packages = mPackageManager.getPackagesForUid(callingUid); + // If any of the packages includes a white listed package, return the full set, + // otherwise return non-shared accounts only. + // This might be a temporary way to specify a whitelist + String whiteList = mContext.getResources().getString( + com.android.internal.R.string.config_appsAuthorizedForSharedAccounts); + for (String packageName : packages) { + if (whiteList.contains(";" + packageName + ";")) { + return unfiltered; + } + } + ArrayList allowed = new ArrayList(); + Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId); + if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered; + for (Account account : unfiltered) { + boolean found = false; + for (Account shared : sharedAccounts) { + if (shared.equals(account)) { + found = true; + break; + } + } + if (!found) { + allowed.add(account); + } + } + Account[] filtered = new Account[allowed.size()]; + allowed.toArray(filtered); + return filtered; + } else { + return unfiltered; + } + } + + protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType, + int callingUid) { if (accountType != null) { final Account[] accounts = userAccounts.accountCache.get(accountType); if (accounts == null) { return EMPTY_ACCOUNT_ARRAY; } else { - return Arrays.copyOf(accounts, accounts.length); + return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length), + callingUid); } } else { int totalLength = 0; @@ -2714,7 +2768,7 @@ public class AccountManagerService accountsOfType.length); totalLength += accountsOfType.length; } - return accounts; + return filterSharedAccounts(userAccounts, accounts, callingUid); } } diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java index 1414cbd8c14f..636b0e59913a 100644 --- a/services/java/com/android/server/pm/UserManagerService.java +++ b/services/java/com/android/server/pm/UserManagerService.java @@ -25,7 +25,10 @@ import android.app.IStopUserCallback; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.RestrictionEntry; +import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -83,11 +86,17 @@ public class UserManagerService extends IUserManager.Stub { private static final String TAG_USERS = "users"; private static final String TAG_USER = "user"; private static final String TAG_RESTRICTIONS = "restrictions"; + private static final String TAG_ENTRY = "entry"; + private static final String TAG_VALUE = "value"; + private static final String ATTR_KEY = "key"; + private static final String ATTR_MULTIPLE = "m"; private static final String USER_INFO_DIR = "system" + File.separator + "users"; private static final String USER_LIST_FILENAME = "userlist.xml"; private static final String USER_PHOTO_FILENAME = "photo.png"; + private static final String RESTRICTIONS_FILE_PREFIX = "res_"; + private static final int MIN_USER_ID = 10; private static final int USER_VERSION = 2; @@ -947,6 +956,151 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public List getApplicationRestrictions(String packageName, int userId) { + if (UserHandle.getCallingUserId() != userId + || Binder.getCallingUid() != getUidForPackage(packageName)) { + checkManageUsersPermission("Only system can get restrictions for other users/apps"); + } + synchronized (mPackagesLock) { + // Read the restrictions from XML + return readApplicationRestrictionsLocked(packageName, userId); + } + } + + @Override + public void setApplicationRestrictions(String packageName, List entries, + int userId) { + if (UserHandle.getCallingUserId() != userId + || Binder.getCallingUid() != getUidForPackage(packageName)) { + checkManageUsersPermission("Only system can set restrictions for other users/apps"); + } + synchronized (mPackagesLock) { + // Write the restrictions to XML + writeApplicationRestrictionsLocked(packageName, entries, userId); + } + } + + private int getUidForPackage(String packageName) { + try { + return mContext.getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_UNINSTALLED_PACKAGES).uid; + } catch (NameNotFoundException nnfe) { + return -1; + } + } + + private List readApplicationRestrictionsLocked(String packageName, + int userId) { + final ArrayList entries = new ArrayList(); + final ArrayList values = new ArrayList(); + + FileInputStream fis = null; + try { + AtomicFile restrictionsFile = + new AtomicFile(new File(Environment.getUserSystemDirectory(userId), + RESTRICTIONS_FILE_PREFIX + packageName + ".xml")); + fis = restrictionsFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read restrictions file " + + restrictionsFile.getBaseFile()); + return entries; + } + + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) { + String key = parser.getAttributeValue(null, ATTR_KEY); + String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE); + if (multiple != null) { + int count = Integer.parseInt(multiple); + while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG + && parser.getName().equals(TAG_VALUE)) { + values.add(parser.nextText().trim()); + count--; + } + } + String [] valueStrings = new String[values.size()]; + values.toArray(valueStrings); + Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + valueStrings); + RestrictionEntry entry = new RestrictionEntry(key, valueStrings); + entries.add(entry); + } else { + String value = parser.nextText().trim(); + Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + value); + RestrictionEntry entry = new RestrictionEntry(key, value); + entries.add(entry); + } + } + } + + } catch (IOException ioe) { + } catch (XmlPullParserException pe) { + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + return entries; + } + + private void writeApplicationRestrictionsLocked(String packageName, + List entries, int userId) { + FileOutputStream fos = null; + AtomicFile restrictionsFile = new AtomicFile( + new File(Environment.getUserSystemDirectory(userId), + RESTRICTIONS_FILE_PREFIX + packageName + ".xml")); + try { + fos = restrictionsFile.startWrite(); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_RESTRICTIONS); + + for (RestrictionEntry entry : entries) { + serializer.startTag(null, TAG_ENTRY); + serializer.attribute(null, ATTR_KEY, entry.key); + if (entry.getStringValue() != null || entry.getMultipleValues() == null) { + String value = entry.getStringValue(); + serializer.text(value != null ? value : ""); + } else { + String[] values = entry.getMultipleValues(); + serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length)); + for (String value : values) { + serializer.startTag(null, TAG_VALUE); + serializer.text(value != null ? value : ""); + serializer.endTag(null, TAG_VALUE); + } + } + serializer.endTag(null, TAG_ENTRY); + } + + serializer.endTag(null, TAG_RESTRICTIONS); + + serializer.endDocument(); + restrictionsFile.finishWrite(fos); + } catch (Exception e) { + restrictionsFile.failWrite(fos); + Slog.e(LOG_TAG, "Error writing application restrictions list"); + } + } + + @Override public int getUserSerialNumber(int userHandle) { synchronized (mPackagesLock) { if (!exists(userHandle)) return -1; -- 2.11.0