diff --git a/api/current.txt b/api/current.txt index 12dc6b4e38e92..f4b4d5320e85e 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 f8b7a0c949be9..313260f65f9f4 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 132388ef3e249..7b074382b447d 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 8a9eed27d4f4d..7dd76cd676f0b 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 @@ -285,6 +286,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 diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 53c47d2abe9d2..e1461e3dde7ee 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2414,6 +2414,15 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PRE_BOOT_COMPLETED = "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 @@ -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 0000000000000..b93eee3a5ff71 --- /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 0000000000000..196460cbd9360 --- /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 85f7aa5113a24..77ca7f68e75e5 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 5eac90373d173..149b8e5d2a958 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 34c9740557a47..4c2d7a6432207 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 51e3e7c937333..7c055283775b9 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 f1d8c03c5e58d..6f59817bf671b 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 42e5cf1ba4ba2..ef0ae03a371f9 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 614ac07165e9b..a763c90d54af0 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 49295f56b3e29..09daf569349b7 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 1414cbd8c14f8..636b0e59913a7 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; @@ -946,6 +955,151 @@ public class UserManagerService extends IUserManager.Stub { parent.delete(); } + @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) {