diff options
author | Amith Yamasani <yamasani@google.com> | 2012-12-14 12:09:36 -0800 |
---|---|---|
committer | Amith Yamasani <yamasani@google.com> | 2013-03-06 09:49:44 -0800 |
commit | 67df64b3a48a8157d08a98fa90135d0ac0ee621c (patch) | |
tree | d390d5c537c976a14688e24c1f9a8a213840ff29 | |
parent | 0ffc81c1ada65b4ef4febaacf044e9fa62309b87 (diff) | |
download | frameworks_base-67df64b3a48a8157d08a98fa90135d0ac0ee621c.zip frameworks_base-67df64b3a48a8157d08a98fa90135d0ac0ee621c.tar.gz frameworks_base-67df64b3a48a8157d08a98fa90135d0ac0ee621c.tar.bz2 |
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
11 files changed, 462 insertions, 54 deletions
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 @@ public final class Pm { 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 @@ public abstract class AbstractAccountAuthenticator { 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 @@ public abstract class AbstractAccountAuthenticator { 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 @@ public class AccountManager { } /** + * 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 @@ oneway interface IAccountAuthenticator { * 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 @@ interface IAccountManager { 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.graphics.drawable.Drawable; 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 @@ final class ApplicationPackageManager extends PackageManager { 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 @@ final class ApplicationPackageManager extends PackageManager { @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 @@ interface IPackageManager { 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 @@ interface IPackageManager { 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 @@ public class UserInfo implements Parcelable { 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.Account; 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.Slog; 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService } } + 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 @@ public class AccountManagerService // 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService } @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 @@ public class AccountManagerService private int mNumRequestContinued = 0; private int mNumErrors = 0; - IAccountAuthenticator mAuthenticator = null; private final boolean mStripAuthTokenFromResult; @@ -1688,7 +1890,7 @@ public class AccountManagerService 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService + 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 @@ public class AccountManagerService " = '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 @@ public class AccountManagerService 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { * @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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { * 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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(); |