Shared accounts and sharing of apps
API and preliminary implementation for sharing primary user accounts with a secondary user.
AbstractAccountAuthenticator has new methods to retrieve and apply a bundle of credentials
to clone an account from the primary to a restricted secondary user. The AccountManagerService
initiates the account clone when it starts up the user and detects that the user has
a shared account registered that hasn't been converted to a real account.
AccountManager also has new hidden APIs to add/remove/get shared accounts. There might be
further improvements to this API to make shared accounts hidden/visible to select apps.
AccountManagerService has a new table to store the shared account information.
Added ability in PackageManager to install and uninstall packages for a secondary user. This
is required when the primary user selects a few apps to share with a restricted user.
Remove shared accounts from secondary users when primary user removes the account.
Change-Id: I9378ed0d8c1cc66baf150a4bec0ede56f6f8b06b
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index da398ef..22ce841 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1094,7 +1094,7 @@
private boolean deletePackage(String pkg, int unInstallFlags) {
PackageDeleteObserver obs = new PackageDeleteObserver();
try {
- mPm.deletePackage(pkg, obs, unInstallFlags);
+ mPm.deletePackageAsUser(pkg, obs, UserHandle.USER_OWNER, unInstallFlags);
synchronized (obs) {
while (!obs.finished) {
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index e9535ab..fa46689 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -275,6 +275,38 @@
handleException(response, "getAccountRemovalAllowed", account.toString(), e);
}
}
+
+ public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
+ Account account) throws RemoteException {
+ checkBinderPermission();
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
+ new AccountAuthenticatorResponse(response), account);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAccountCredentialsForCloning", account.toString(), e);
+ }
+ }
+
+ public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws RemoteException {
+ checkBinderPermission();
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.addAccountFromCredentials(
+ new AccountAuthenticatorResponse(response), account,
+ accountCredentials);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "addAccountFromCredentials", account.toString(), e);
+ }
+ }
}
private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -471,4 +503,54 @@
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
return result;
}
+
+ /**
+ * @hide
+ * Returns a Bundle that contains whatever is required to clone the account on a different
+ * user. The Bundle is passed to the authenticator instance in the target user via
+ * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}.
+ * The default implementation returns null, indicating that cloning is not supported.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}
+ */
+ public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
+ final Account account) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
+
+ /**
+ * @hide
+ * Creates an account based on credentials provided by the authenticator instance of another
+ * user on the device, who has chosen to share the account with this user.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @param accountCredentials the Bundle containing the required credentials to create the
+ * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is
+ * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}
+ */
+ public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 6d9bb1d..6aac723 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1123,6 +1123,57 @@
}
/**
+ * Adds a shared account from the primary user to a secondary user. Adding the shared account
+ * doesn't take effect immediately. When the target user starts up, any pending shared accounts
+ * are attempted to be copied to the target user from the primary via calls to the
+ * authenticator.
+ * @param account the account to share
+ * @param user the target user
+ * @return
+ * @hide
+ */
+ public boolean addSharedAccount(final Account account, UserHandle user) {
+ try {
+ boolean val = mService.addSharedAccountAsUser(account, user.getIdentifier());
+ return val;
+ } catch (RemoteException re) {
+ // won't ever happen
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * @hide
+ * Removes the shared account.
+ * @param account the account to remove
+ * @param user the user to remove the account from
+ * @return
+ */
+ public boolean removeSharedAccount(final Account account, UserHandle user) {
+ try {
+ boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
+ return val;
+ } catch (RemoteException re) {
+ // won't ever happen
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * @hide
+ * @param user
+ * @return
+ */
+ public Account[] getSharedAccounts(UserHandle user) {
+ try {
+ return mService.getSharedAccountsAsUser(user.getIdentifier());
+ } catch (RemoteException re) {
+ // won't ever happen
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
* Confirms that the user knows the password for an account to make extra
* sure they are the owner of the account. The user-entered password can
* be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 8860710..58612da 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -70,4 +70,17 @@
* Gets whether or not the account is allowed to be removed.
*/
void getAccountRemovalAllowed(in IAccountAuthenticatorResponse response, in Account account);
+
+ /**
+ * Returns a Bundle containing the required credentials to copy the account across users.
+ */
+ void getAccountCredentialsForCloning(in IAccountAuthenticatorResponse response,
+ in Account account);
+
+ /**
+ * Uses the Bundle containing credentials from another instance of the authenticator to create
+ * a copy of the account on this user.
+ */
+ void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
+ in Bundle accountCredentials);
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index dbb4924..47b257d 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -58,4 +58,9 @@
in Bundle options, boolean expectActivityLaunch, int userId);
void getAuthTokenLabel(in IAccountManagerResponse response, String accountType,
String authTokenType);
+
+ /* Shared accounts */
+ boolean addSharedAccountAsUser(in Account account, int userId);
+ Account[] getSharedAccountsAsUser(int userId);
+ boolean removeSharedAccountAsUser(in Account account, int userId);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f09c2fe..6d55dd5 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -50,6 +50,7 @@
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -1064,7 +1065,7 @@
public int installExistingPackage(String packageName)
throws NameNotFoundException {
try {
- int res = mPM.installExistingPackage(packageName);
+ int res = mPM.installExistingPackageAsUser(packageName, UserHandle.myUserId());
if (res == INSTALL_FAILED_INVALID_URI) {
throw new NameNotFoundException("Package " + packageName + " doesn't exist");
}
@@ -1126,7 +1127,7 @@
@Override
public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
try {
- mPM.deletePackage(packageName, observer, flags);
+ mPM.deletePackageAsUser(packageName, observer, UserHandle.myUserId(), flags);
} catch (RemoteException e) {
// Should never happen!
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a368451..a32a201 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -194,20 +194,22 @@
void setInstallerPackageName(in String targetPackage, in String installerPackageName);
/**
- * Delete a package.
+ * Delete a package for a specific user.
*
* @param packageName The fully qualified name of the package to delete.
* @param observer a callback to use to notify when the package deletion in finished.
+ * @param userId the id of the user for whom to delete the package
* @param flags - possible values: {@link #DONT_DELETE_DATA}
*/
- void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags);
+ void deletePackageAsUser(in String packageName, IPackageDeleteObserver observer,
+ int userId, int flags);
String getInstallerPackageName(in String packageName);
void addPackageToPreferred(String packageName);
-
+
void removePackageFromPreferred(String packageName);
-
+
List<PackageInfo> getPreferredPackages(int flags);
void resetPreferredActivities(int userId);
@@ -381,7 +383,7 @@
in VerificationParams verificationParams,
in ContainerEncryptionParams encryptionParams);
- int installExistingPackage(String packageName);
+ int installExistingPackageAsUser(String packageName, int userId);
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 593f826..4c87830 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -97,6 +97,10 @@
return (flags & FLAG_GUEST) == FLAG_GUEST;
}
+ public boolean isRestricted() {
+ return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED;
+ }
+
public UserInfo() {
}
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 2a62c17..49295f5 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -21,7 +21,6 @@
import android.accounts.AccountAndUser;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
-import android.accounts.AccountManagerResponse;
import android.accounts.AuthenticatorDescription;
import android.accounts.GrantCredentialsPermissionActivity;
import android.accounts.IAccountAuthenticator;
@@ -70,6 +69,7 @@
import android.util.SparseArray;
import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
@@ -103,7 +103,7 @@
private static final int TIMEOUT_DELAY_MS = 1000 * 60;
private static final String DATABASE_NAME = "accounts.db";
- private static final int DATABASE_VERSION = 4;
+ private static final int DATABASE_VERSION = 5;
private final Context mContext;
@@ -146,6 +146,8 @@
private static final String META_KEY = "key";
private static final String META_VALUE = "value";
+ private static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
+
private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
private static final Intent ACCOUNTS_CHANGED_INTENT;
@@ -249,12 +251,18 @@
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new BroadcastReceiver() {
+ userFilter.addAction(Intent.ACTION_USER_STARTED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent);
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ onUserRemoved(intent);
+ } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+ onUserStarted(intent);
+ }
}
- }, userFilter);
+ }, UserHandle.ALL, userFilter, null, null);
}
public void systemReady() {
@@ -430,6 +438,21 @@
}
}
+ private void onUserStarted(Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId < 1) return;
+
+ // Check if there's a shared account that needs to be created as an account
+ Account[] sharedAccounts = getSharedAccountsAsUser(userId);
+ if (sharedAccounts == null || sharedAccounts.length == 0) return;
+ Account[] accounts = getAccountsAsUser(null, userId);
+ for (Account sa : sharedAccounts) {
+ if (ArrayUtils.contains(accounts, sa)) continue;
+ // Account doesn't exist. Copy it now.
+ copyAccountToUser(sa, UserHandle.USER_OWNER, userId);
+ }
+ }
+
@Override
public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
Slog.d(TAG, "onServiceChanged() for userId " + userId);
@@ -535,14 +558,120 @@
// fails if the account already exists
long identityToken = clearCallingIdentity();
try {
- return addAccountInternal(accounts, account, password, extras);
+ return addAccountInternal(accounts, account, password, extras, false);
} finally {
restoreCallingIdentity(identityToken);
}
}
+ private boolean copyAccountToUser(final Account account, int userFrom, int userTo) {
+ final UserAccounts fromAccounts = getUserAccounts(userFrom);
+ final UserAccounts toAccounts = getUserAccounts(userTo);
+ if (fromAccounts == null || toAccounts == null) {
+ return false;
+ }
+
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(fromAccounts, null, account.type, false,
+ false /* stripAuthTokenFromResult */) {
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAccountCredentialsForClone"
+ + ", " + account.type;
+ }
+
+ public void run() throws RemoteException {
+ mAuthenticator.getAccountCredentialsForCloning(this, account);
+ }
+
+ public void onResult(Bundle result) {
+ 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);
+ }
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return true;
+ }
+
+ // TODO: Remove fallback - move to authenticator
+ private void clonePassword(UserAccounts fromAccounts, UserAccounts toAccounts,
+ Account account) {
+ long id = clearCallingIdentity();
+ try {
+ String password = readPasswordInternal(fromAccounts, account);
+ String extraFlags = readUserDataInternal(fromAccounts, account, "flags");
+ String extraServices = readUserDataInternal(fromAccounts, account, "services");
+ Bundle extras = new Bundle();
+ extras.putString("flags", extraFlags);
+ extras.putString("services", extraServices);
+ addAccountInternal(toAccounts, account, password, extras, true);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+
+ void completeCloningAccount(final Bundle result, final Account account,
+ final UserAccounts targetUser) {
+ long id = clearCallingIdentity();
+ try {
+ new Session(targetUser, null, account.type, false,
+ false /* stripAuthTokenFromResult */) {
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAccountCredentialsForClone"
+ + ", " + account.type;
+ }
+
+ public void run() throws RemoteException {
+ mAuthenticator.addAccountFromCredentials(this, account, result);
+ }
+
+ public void onResult(Bundle result) {
+ 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);
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ super.onError(errorCode, errorMessage);
+ // TODO: Show error notification to user
+ // TODO: Should we remove the shadow account so that it doesn't keep trying?
+ }
+
+ }.bind();
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+
private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
- Bundle extras) {
+ Bundle extras, boolean restricted) {
if (account == null) {
return false;
}
@@ -768,6 +897,21 @@
removeAccountFromCacheLocked(accounts, account);
sendAccountsChangedBroadcast(accounts.userId);
}
+ if (accounts.userId == UserHandle.USER_OWNER) {
+ // Owner's account was removed, remove from any users that are sharing
+ // this account.
+ long id = Binder.clearCallingIdentity();
+ try {
+ List<UserInfo> users = mUserManager.getUsers(true);
+ for (UserInfo user : users) {
+ if (!user.isPrimary() && user.isRestricted()) {
+ removeSharedAccountAsUser(account, user.id);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
}
public void invalidateAuthToken(String accountType, String authToken) {
@@ -1606,6 +1750,65 @@
}
@Override
+ public boolean addSharedAccountAsUser(Account account, int userId) {
+ userId = handleIncomingUser(userId);
+ SQLiteDatabase db = getUserAccounts(userId).openHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, account.name);
+ values.put(ACCOUNTS_TYPE, account.type);
+ db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[] {account.name, account.type});
+ long accountId = db.insert(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
+ if (accountId < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping the DB insert failed");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean removeSharedAccountAsUser(Account account, int userId) {
+ userId = handleIncomingUser(userId);
+ UserAccounts accounts = getUserAccounts(userId);
+ SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ int r = db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[] {account.name, account.type});
+ if (r > 0) {
+ removeAccountInternal(accounts, account);
+ }
+ return r > 0;
+ }
+
+ @Override
+ public Account[] getSharedAccountsAsUser(int userId) {
+ userId = handleIncomingUser(userId);
+ UserAccounts accounts = getUserAccounts(userId);
+ ArrayList<Account> accountList = new ArrayList<Account>();
+ Cursor cursor = null;
+ try {
+ cursor = accounts.openHelper.getReadableDatabase()
+ .query(TABLE_SHARED_ACCOUNTS, new String[]{ACCOUNTS_NAME, ACCOUNTS_TYPE},
+ null, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
+ int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
+ do {
+ accountList.add(new Account(cursor.getString(nameIndex),
+ cursor.getString(typeIndex)));
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ Account[] accountArray = new Account[accountList.size()];
+ accountList.toArray(accountArray);
+ return accountArray;
+ }
+
+ @Override
public Account[] getAccounts(String type) {
return getAccountsAsUser(type, UserHandle.getCallingUserId());
}
@@ -1679,7 +1882,6 @@
private int mNumRequestContinued = 0;
private int mNumErrors = 0;
-
IAccountAuthenticator mAuthenticator = null;
private final boolean mStripAuthTokenFromResult;
@@ -1688,7 +1890,7 @@
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
super();
- if (response == null) throw new IllegalArgumentException("response is null");
+ //if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
mAccounts = accounts;
mStripAuthTokenFromResult = stripAuthTokenFromResult;
@@ -1699,11 +1901,13 @@
synchronized (mSessions) {
mSessions.put(toString(), this);
}
- try {
- response.asBinder().linkToDeath(this, 0 /* flags */);
- } catch (RemoteException e) {
- mResponse = null;
- binderDied();
+ if (response != null) {
+ try {
+ response.asBinder().linkToDeath(this, 0 /* flags */);
+ } catch (RemoteException e) {
+ mResponse = null;
+ binderDied();
+ }
}
}
@@ -2011,9 +2215,19 @@
+ META_KEY + " TEXT PRIMARY KEY NOT NULL, "
+ META_VALUE + " TEXT)");
+ createSharedAccountsTable(db);
+
createAccountsDeletionTrigger(db);
}
+ private void createSharedAccountsTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
+ + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ACCOUNTS_NAME + " TEXT NOT NULL, "
+ + ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
+ }
+
private void createAccountsDeletionTrigger(SQLiteDatabase db) {
db.execSQL(""
+ " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
@@ -2058,6 +2272,15 @@
" = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
oldVersion++;
}
+
+ if (oldVersion == 4) {
+ createSharedAccountsTable(db);
+ oldVersion++;
+ }
+
+ if (oldVersion != newVersion) {
+ Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
+ }
}
@Override
@@ -2216,6 +2439,16 @@
throw new SecurityException(msg);
}
+ private int handleIncomingUser(int userId) {
+ try {
+ return ActivityManagerNative.getDefault().handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, "", null);
+ } catch (RemoteException re) {
+ // Shouldn't happen, local.
+ }
+ return userId;
+ }
+
private boolean inSystemImage(int callingUid) {
final int callingUserId = UserHandle.getUserId(callingUid);
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 7fb8902..cfb0f3f 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -5652,7 +5652,7 @@
null);
final int uid = Binder.getCallingUid();
- if (!isUserAllowed(uid, UserManager.ALLOW_INSTALL_APPS)) {
+ if (!isUserAllowed(UserHandle.getUserId(uid), UserManager.ALLOW_INSTALL_APPS)) {
try {
observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
@@ -5690,13 +5690,17 @@
* @hide
*/
@Override
- public int installExistingPackage(String packageName) {
+ public int installExistingPackageAsUser(String packageName, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
PackageSetting pkgSetting;
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
- if (!isUserAllowed(uid, UserManager.ALLOW_INSTALL_APPS)) {
+ if (UserHandle.getUserId(uid) != userId) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "installExistingPackage for user " + userId);
+ }
+ if (!isUserAllowed(userId, UserManager.ALLOW_INSTALL_APPS)) {
return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
}
@@ -5730,14 +5734,11 @@
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isUserAllowed(int callingUid, String restrictionKey) {
- if (callingUid != android.os.Process.myUid()) {
- Bundle restrictions = sUserManager.getUserRestrictions(
- UserHandle.getUserId(callingUid));
- if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
- Log.w(TAG, "User does not have permission to: " + restrictionKey);
- return false;
- }
+ private boolean isUserAllowed(int userId, String restrictionKey) {
+ Bundle restrictions = sUserManager.getUserRestrictions(userId);
+ if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
+ Log.w(TAG, "User does not have permission to: " + restrictionKey);
+ return false;
}
return true;
}
@@ -8090,14 +8091,19 @@
return tmpPackageFile;
}
- public void deletePackage(final String packageName,
- final IPackageDeleteObserver observer,
- final int flags) {
+ @Override
+ public void deletePackageAsUser(final String packageName,
+ final IPackageDeleteObserver observer,
+ final int userId, final int flags) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
- // Queue up an async operation since the package deletion may take a little while.
final int uid = Binder.getCallingUid();
- if (!isUserAllowed(uid, UserManager.ALLOW_UNINSTALL_APPS)) {
+ if (UserHandle.getUserId(uid) != userId) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "deletePackage for user " + userId);
+ }
+ if (!isUserAllowed(userId, UserManager.ALLOW_UNINSTALL_APPS)) {
try {
observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
@@ -8105,10 +8111,11 @@
return;
}
+ // Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
- final int returnCode = deletePackageX(packageName, uid, flags);
+ final int returnCode = deletePackageX(packageName, userId, flags);
if (observer != null) {
try {
observer.packageDeleted(packageName, returnCode);
@@ -8134,14 +8141,14 @@
* persisting settings for later use
* sending a broadcast if necessary
*/
- private int deletePackageX(String packageName, int uid, int flags) {
+ private int deletePackageX(String packageName, int userId, int flags) {
final PackageRemovedInfo info = new PackageRemovedInfo();
final boolean res;
IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
- if (dpm != null && dpm.packageHasActiveAdmins(packageName, UserHandle.getUserId(uid))) {
+ if (dpm != null && dpm.packageHasActiveAdmins(packageName, userId)) {
Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
}
@@ -8153,7 +8160,7 @@
synchronized (mInstallLock) {
res = deletePackageLI(packageName,
(flags & PackageManager.DELETE_ALL_USERS) != 0
- ? UserHandle.ALL : new UserHandle(UserHandle.getUserId(uid)),
+ ? UserHandle.ALL : new UserHandle(userId),
true, flags | REMOVE_CHATTY, info, true);
systemUpdate = info.isRemovedPackageSystemUpdate;
if (res && !systemUpdate && mPackages.get(packageName) == null) {
@@ -8376,7 +8383,7 @@
Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
}
- if (!isSystemApp(ps) && user != null
+ if (user != null
&& user.getIdentifier() != UserHandle.USER_ALL) {
// The caller is asking that the package only be deleted for a single
// user. To do this, we just mark its uninstalled state and delete
@@ -8387,17 +8394,27 @@
true, //stopped
true, //notLaunched
null, null);
- if (ps.isAnyInstalled(sUserManager.getUserIds())) {
- // Other user still have this package installed, so all
+ if (!isSystemApp(ps)) {
+ if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+ // Other user still have this package installed, so all
+ // we need to do is clear this user's data and save that
+ // it is uninstalled.
+ removeUser = user.getIdentifier();
+ appId = ps.appId;
+ mSettings.writePackageRestrictionsLPr(removeUser);
+ } else {
+ // We need to set it back to 'installed' so the uninstall
+ // broadcasts will be sent correctly.
+ ps.setInstalled(true, user.getIdentifier());
+ }
+ } else {
+ // This is a system app, so we assume that the
+ // other users still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
removeUser = user.getIdentifier();
appId = ps.appId;
mSettings.writePackageRestrictionsLPr(removeUser);
- } else {
- // We need to set it back to 'installed' so the uninstall
- // broadcasts will be sent correctly.
- ps.setInstalled(true, user.getIdentifier());
}
}
}
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index c3f4256..1414cbd 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -542,16 +542,16 @@
private void fallbackToSingleUserLocked() {
// Create the primary user
- UserInfo primary = new UserInfo(0,
+ UserInfo primary = new UserInfo(UserHandle.USER_OWNER,
mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED);
mUsers.put(0, primary);
mNextSerialNumber = MIN_USER_ID;
-
+
Bundle restrictions = new Bundle();
initRestrictionsToDefaults(restrictions);
- mUserRestrictions.append(0, restrictions);
-
+ mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
+
updateUserIdsLocked();
writeUserListLocked();