From 72ed12c55f36c61f3ec07ab563ed8c93ea92c733 Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Wed, 7 Sep 2016 09:57:41 -0700 Subject: [PATCH] [DO NOT MERGE] Backup account access grants Sync adapters without an account access cannot run until the user approves the account access (for the case the account access is not allowed by other policy such as being singed with the same cert as the authenticator). If the sync adapter does not have permission to access the account we ask the user to grant access and take a note. This CL adds backup for the explicit user grants. bug:31162498 Change-Id: I31e3f3d010475352c7c54255ac2d3a2fed4d0c72 --- .../android/accounts/AccountManagerInternal.java | 14 + core/java/android/util/PackageUtils.java | 96 +++++++ .../server/backup/AccountManagerBackupHelper.java | 85 ++++++ .../android/server/backup/SystemBackupAgent.java | 3 + .../accounts/AccountManagerBackupHelper.java | 312 +++++++++++++++++++++ .../server/accounts/AccountManagerService.java | 65 +++-- 6 files changed, 552 insertions(+), 23 deletions(-) create mode 100644 core/java/android/util/PackageUtils.java create mode 100644 core/java/com/android/server/backup/AccountManagerBackupHelper.java create mode 100644 services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java diff --git a/core/java/android/accounts/AccountManagerInternal.java b/core/java/android/accounts/AccountManagerInternal.java index 0ac719777e20..68c17c32fdc3 100644 --- a/core/java/android/accounts/AccountManagerInternal.java +++ b/core/java/android/accounts/AccountManagerInternal.java @@ -73,4 +73,18 @@ public abstract class AccountManagerInternal { */ public abstract void addOnAppPermissionChangeListener( @NonNull OnAppPermissionChangeListener listener); + + /** + * Backups the account access permissions. + * @param userId The user for which to backup. + * @return The backup data. + */ + public abstract byte[] backupAccountAccessPermissions(int userId); + + /** + * Restores the account access permissions. + * @param data The restore data. + * @param userId The user for which to restore. + */ + public abstract void restoreAccountAccessPermissions(byte[] data, int userId); } diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java new file mode 100644 index 000000000000..6531aef9a50c --- /dev/null +++ b/core/java/android/util/PackageUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 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.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Helper functions applicable to packages. + * @hide + */ +public final class PackageUtils { + private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + private PackageUtils() { + /* hide constructor */ + } + + /** + * Computes the SHA256 digest of the signing cert for a package. + * @param packageManager The package manager. + * @param packageName The package for which to generate the digest. + * @param userId The user for which to generate the digest. + * @return The digest or null if the package does not exist for this user. + */ + public static @Nullable String computePackageCertSha256Digest( + @NonNull PackageManager packageManager, + @NonNull String packageName, int userId) { + final PackageInfo packageInfo; + try { + packageInfo = packageManager.getPackageInfoAsUser(packageName, + PackageManager.GET_SIGNATURES, userId); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + return computeCertSha256Digest(packageInfo.signatures[0]); + } + + /** + * Computes the SHA256 digest of a cert. + * @param signature The signature. + * @return The digest or null if an error occurs. + */ + public static @Nullable String computeCertSha256Digest(@NonNull Signature signature) { + return computeSha256Digest(signature.toByteArray()); + } + + /** + * Computes the SHA256 digest of some data. + * @param data The data. + * @return The digest or null if an error occurs. + */ + public static @Nullable String computeSha256Digest(@NonNull byte[] data) { + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance("SHA256"); + } catch (NoSuchAlgorithmException e) { + /* can't happen */ + return null; + } + + messageDigest.update(data); + + final byte[] digest = messageDigest.digest(); + final int digestLength = digest.length; + final int charCount = 2 * digestLength; + + final char[] chars = new char[charCount]; + for (int i = 0; i < digestLength; i++) { + final int byteHex = digest[i] & 0xFF; + chars[i * 2] = HEX_ARRAY[byteHex >>> 4]; + chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F]; + } + return new String(chars); + } +} diff --git a/core/java/com/android/server/backup/AccountManagerBackupHelper.java b/core/java/com/android/server/backup/AccountManagerBackupHelper.java new file mode 100644 index 000000000000..39b18c0f2c16 --- /dev/null +++ b/core/java/com/android/server/backup/AccountManagerBackupHelper.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 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 com.android.server.backup; + +import android.accounts.AccountManagerInternal; +import android.app.backup.BlobBackupHelper; +import android.os.UserHandle; +import android.util.Slog; +import com.android.server.LocalServices; + +/** + * Helper for handling backup of account manager specific state. + */ +public class AccountManagerBackupHelper extends BlobBackupHelper { + private static final String TAG = "AccountsBackup"; + private static final boolean DEBUG = false; + + // current schema of the backup state blob + private static final int STATE_VERSION = 1; + + // key under which the account access grant state blob is committed to backup + private static final String KEY_ACCOUNT_ACCESS_GRANTS = "account_access_grants"; + + public AccountManagerBackupHelper() { + super(STATE_VERSION, KEY_ACCOUNT_ACCESS_GRANTS); + } + + @Override + protected byte[] getBackupPayload(String key) { + AccountManagerInternal am = LocalServices.getService(AccountManagerInternal.class); + if (DEBUG) { + Slog.d(TAG, "Handling backup of " + key); + } + try { + switch (key) { + case KEY_ACCOUNT_ACCESS_GRANTS: { + return am.backupAccountAccessPermissions(UserHandle.USER_SYSTEM); + } + + default: { + Slog.w(TAG, "Unexpected backup key " + key); + } + } + } catch (Exception e) { + Slog.e(TAG, "Unable to store payload " + key); + } + + return new byte[0]; + } + + @Override + protected void applyRestoredPayload(String key, byte[] payload) { + AccountManagerInternal am = LocalServices.getService(AccountManagerInternal.class); + if (DEBUG) { + Slog.d(TAG, "Handling restore of " + key); + } + try { + switch (key) { + case KEY_ACCOUNT_ACCESS_GRANTS: { + am.restoreAccountAccessPermissions(payload, UserHandle.USER_SYSTEM); + } break; + + default: { + Slog.w(TAG, "Unexpected restore key " + key); + } + } + } catch (Exception e) { + Slog.w(TAG, "Unable to restore key " + key); + } + } +} diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java index 9d296fa19400..537565185d9b 100644 --- a/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/core/java/com/android/server/backup/SystemBackupAgent.java @@ -49,6 +49,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String PERMISSION_HELPER = "permissions"; private static final String USAGE_STATS_HELPER = "usage_stats"; private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager"; + private static final String ACCOUNT_MANAGER_HELPER = "account_manager"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are @@ -82,6 +83,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper(PERMISSION_HELPER, new PermissionBackupHelper()); addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this)); addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper()); + addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper()); super.onBackup(oldState, data, newState); } @@ -111,6 +113,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper(PERMISSION_HELPER, new PermissionBackupHelper()); addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this)); addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper()); + addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper()); try { super.onRestore(data, appVersionCode, newState); diff --git a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java new file mode 100644 index 000000000000..087ec611dd80 --- /dev/null +++ b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2016 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 com.android.server.accounts; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerInternal; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.util.PackageUtils; +import android.util.Xml; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for backup and restore of account access grants. + */ +public final class AccountManagerBackupHelper { + private static final String TAG = "AccountManagerBackupHelper"; + + private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour + + private static final String TAG_PERMISSIONS = "permissions"; + private static final String TAG_PERMISSION = "permission"; + private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256"; + private static final String ATTR_PACKAGE = "package"; + private static final String ATTR_DIGEST = "digest"; + + private static final String ACCOUNT_ACCESS_GRANTS = "" + + "SELECT " + AccountManagerService.ACCOUNTS_NAME + ", " + + AccountManagerService.GRANTS_GRANTEE_UID + + " FROM " + AccountManagerService.TABLE_ACCOUNTS + + ", " + AccountManagerService.TABLE_GRANTS + + " WHERE " + AccountManagerService.GRANTS_ACCOUNTS_ID + + "=" + AccountManagerService.ACCOUNTS_ID; + + private final Object mLock = new Object(); + + private final AccountManagerService mAccountManagerService; + private final AccountManagerInternal mAccountManagerInternal; + + @GuardedBy("mLock") + private List mRestorePendingAppPermissions; + + @GuardedBy("mLock") + private RestorePackageMonitor mRestorePackageMonitor; + + @GuardedBy("mLock") + private Runnable mRestoreCancelCommand; + + public AccountManagerBackupHelper(AccountManagerService accountManagerService, + AccountManagerInternal accountManagerInternal) { + mAccountManagerService = accountManagerService; + mAccountManagerInternal = accountManagerInternal; + } + + private final class PendingAppPermission { + private final @NonNull String accountDigest; + private final @NonNull String packageName; + private final @NonNull String certDigest; + private final @IntRange(from = 0) int userId; + + public PendingAppPermission(String accountDigest, String packageName, + String certDigest, int userId) { + this.accountDigest = accountDigest; + this.packageName = packageName; + this.certDigest = certDigest; + this.userId = userId; + } + + public boolean apply(PackageManager packageManager) { + Account account = null; + AccountManagerService.UserAccounts accounts = mAccountManagerService + .getUserAccounts(userId); + synchronized (accounts.cacheLock) { + for (Account[] accountsPerType : accounts.accountCache.values()) { + for (Account accountPerType : accountsPerType) { + if (accountDigest.equals(PackageUtils.computeSha256Digest( + accountPerType.name.getBytes()))) { + account = accountPerType; + break; + } + } + if (account != null) { + break; + } + } + } + if (account == null) { + return false; + } + final PackageInfo packageInfo; + try { + packageInfo = packageManager.getPackageInfoAsUser(packageName, + PackageManager.GET_SIGNATURES, userId); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + String currentCertDigest = PackageUtils.computeCertSha256Digest( + packageInfo.signatures[0]); + if (!certDigest.equals(currentCertDigest)) { + return false; + } + final int uid = packageInfo.applicationInfo.uid; + if (!mAccountManagerInternal.hasAccountAccess(account, uid)) { + mAccountManagerService.grantAppPermission(account, + AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid); + } + return true; + } + } + + public byte[] backupAccountAccessPermissions(int userId) { + final AccountManagerService.UserAccounts accounts = mAccountManagerService + .getUserAccounts(userId); + synchronized (accounts.cacheLock) { + SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + try ( + Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null); + ) { + if (cursor == null || !cursor.moveToFirst()) { + return null; + } + + final int nameColumnIdx = cursor.getColumnIndex( + AccountManagerService.ACCOUNTS_NAME); + final int uidColumnIdx = cursor.getColumnIndex( + AccountManagerService.GRANTS_GRANTEE_UID); + + ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); + try { + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(dataStream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_PERMISSIONS); + + PackageManager packageManager = mAccountManagerService.mContext + .getPackageManager(); + + do { + final String accountName = cursor.getString(nameColumnIdx); + final int uid = cursor.getInt(uidColumnIdx); + + final String[] packageNames = packageManager.getPackagesForUid(uid); + if (packageNames == null) { + continue; + } + + for (String packageName : packageNames) { + String digest = PackageUtils.computePackageCertSha256Digest( + packageManager, packageName, userId); + if (digest != null) { + serializer.startTag(null, TAG_PERMISSION); + serializer.attribute(null, ATTR_ACCOUNT_SHA_256, + PackageUtils.computeSha256Digest(accountName.getBytes())); + serializer.attribute(null, ATTR_PACKAGE, packageName); + serializer.attribute(null, ATTR_DIGEST, digest); + serializer.endTag(null, TAG_PERMISSION); + } + } + } while (cursor.moveToNext()); + + serializer.endTag(null, TAG_PERMISSIONS); + serializer.endDocument(); + serializer.flush(); + } catch (IOException e) { + Log.e(TAG, "Error backing up account access grants", e); + return null; + } + + return dataStream.toByteArray(); + } + } + } + + public void restoreAccountAccessPermissions(byte[] data, int userId) { + try { + ByteArrayInputStream dataStream = new ByteArrayInputStream(data); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(dataStream, StandardCharsets.UTF_8.name()); + PackageManager packageManager = mAccountManagerService.mContext.getPackageManager(); + + final int permissionsOuterDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) { + if (!TAG_PERMISSIONS.equals(parser.getName())) { + continue; + } + final int permissionOuterDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) { + if (!TAG_PERMISSION.equals(parser.getName())) { + continue; + } + String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256); + if (TextUtils.isEmpty(accountDigest)) { + XmlUtils.skipCurrentTag(parser); + } + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); + if (TextUtils.isEmpty(packageName)) { + XmlUtils.skipCurrentTag(parser); + } + String digest = parser.getAttributeValue(null, ATTR_DIGEST); + if (TextUtils.isEmpty(digest)) { + XmlUtils.skipCurrentTag(parser); + } + + PendingAppPermission pendingAppPermission = new PendingAppPermission( + accountDigest, packageName, digest, userId); + + if (!pendingAppPermission.apply(packageManager)) { + synchronized (mLock) { + // Start watching before add pending to avoid a missed signal + if (mRestorePackageMonitor == null) { + mRestorePackageMonitor = new RestorePackageMonitor(); + mRestorePackageMonitor.register(mAccountManagerService.mContext, + mAccountManagerService.mMessageHandler.getLooper(), true); + } + if (mRestorePendingAppPermissions == null) { + mRestorePendingAppPermissions = new ArrayList<>(); + } + mRestorePendingAppPermissions.add(pendingAppPermission); + } + } + } + } + + // Make sure we eventually prune the in-memory pending restores + mRestoreCancelCommand = new CancelRestoreCommand(); + mAccountManagerService.mMessageHandler.postDelayed(mRestoreCancelCommand, + PENDING_RESTORE_TIMEOUT_MILLIS); + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Error restoring app permissions", e); + } + } + + private final class RestorePackageMonitor extends PackageMonitor { + @Override + public void onPackageAdded(String packageName, int uid) { + synchronized (mLock) { + if (mRestorePendingAppPermissions == null) { + return; + } + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) { + return; + } + final int count = mRestorePendingAppPermissions.size(); + for (int i = count - 1; i >= 0; i--) { + PendingAppPermission pendingAppPermission = + mRestorePendingAppPermissions.get(i); + if (!pendingAppPermission.packageName.equals(packageName)) { + continue; + } + if (pendingAppPermission.apply( + mAccountManagerService.mContext.getPackageManager())) { + mRestorePendingAppPermissions.remove(i); + } + } + if (mRestorePendingAppPermissions.isEmpty() + && mRestoreCancelCommand != null) { + mAccountManagerService.mMessageHandler.removeCallbacks(mRestoreCancelCommand); + mRestoreCancelCommand.run(); + mRestoreCancelCommand = null; + } + } + } + } + + private final class CancelRestoreCommand implements Runnable { + @Override + public void run() { + synchronized (mLock) { + mRestorePendingAppPermissions = null; + if (mRestorePackageMonitor != null) { + mRestorePackageMonitor.unregister(); + mRestorePackageMonitor = null; + } + } + } + } +} diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index b86da67715d6..da890c82ae1e 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -89,12 +89,14 @@ import android.os.UserManager; import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.Log; +import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.util.ArrayUtils; @@ -157,13 +159,6 @@ public class AccountManagerService } @Override - public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { - mService.systemReady(); - } - } - - @Override public void onUnlockUser(int userHandle) { mService.onUnlockUser(userHandle); } @@ -176,13 +171,13 @@ public class AccountManagerService private static final int MAX_DEBUG_DB_SIZE = 64; - private final Context mContext; + final Context mContext; private final PackageManager mPackageManager; private final AppOpsManager mAppOpsManager; private UserManager mUserManager; - private final MessageHandler mMessageHandler; + final MessageHandler mMessageHandler; // Messages that can be sent on mHandler private static final int MESSAGE_TIMED_OUT = 3; @@ -190,9 +185,9 @@ public class AccountManagerService private final IAccountAuthenticatorCache mAuthenticatorCache; - private static final String TABLE_ACCOUNTS = "accounts"; - private static final String ACCOUNTS_ID = "_id"; - private static final String ACCOUNTS_NAME = "name"; + static final String TABLE_ACCOUNTS = "accounts"; + static final String ACCOUNTS_ID = "_id"; + static final String ACCOUNTS_NAME = "name"; private static final String ACCOUNTS_TYPE = "type"; private static final String ACCOUNTS_TYPE_COUNT = "count(type)"; private static final String ACCOUNTS_PASSWORD = "password"; @@ -206,10 +201,10 @@ public class AccountManagerService private static final String AUTHTOKENS_TYPE = "type"; private static final String AUTHTOKENS_AUTHTOKEN = "authtoken"; - private static final String TABLE_GRANTS = "grants"; - private static final String GRANTS_ACCOUNTS_ID = "accounts_id"; + static final String TABLE_GRANTS = "grants"; + static final String GRANTS_ACCOUNTS_ID = "accounts_id"; private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type"; - private static final String GRANTS_GRANTEE_UID = "uid"; + static final String GRANTS_GRANTEE_UID = "uid"; private static final String TABLE_EXTRAS = "extras"; private static final String EXTRAS_ID = "_id"; @@ -276,15 +271,15 @@ public class AccountManagerService static class UserAccounts { private final int userId; - private final DeDatabaseHelper openHelper; + final DeDatabaseHelper openHelper; private final HashMap, Integer>, Integer> credentialsPermissionNotificationIds = new HashMap, Integer>, Integer>(); private final HashMap signinRequiredNotificationIds = new HashMap(); - private final Object cacheLock = new Object(); + final Object cacheLock = new Object(); /** protected by the {@link #cacheLock} */ - private final HashMap accountCache = + final HashMap accountCache = new LinkedHashMap<>(); /** protected by the {@link #cacheLock} */ private final HashMap> userDataCache = @@ -526,9 +521,6 @@ public class AccountManagerService } } - public void systemReady() { - } - private UserManager getUserManager() { if (mUserManager == null) { mUserManager = UserManager.get(mContext); @@ -4453,7 +4445,7 @@ public class AccountManagerService } } - private class MessageHandler extends Handler { + class MessageHandler extends Handler { MessageHandler(Looper looper) { super(looper); } @@ -5604,7 +5596,7 @@ public class AccountManagerService * which is in the system. This means we don't need to protect it with permissions. * @hide */ - private void grantAppPermission(Account account, String authTokenType, int uid) { + void grantAppPermission(Account account, String authTokenType, int uid) { if (account == null || authTokenType == null) { Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); return; @@ -5989,6 +5981,11 @@ public class AccountManagerService } private final class AccountManagerInternalImpl extends AccountManagerInternal { + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private AccountManagerBackupHelper mBackupHelper; + @Override public void requestAccountAccess(@NonNull Account account, @NonNull String packageName, @IntRange(from = 0) int userId, @NonNull RemoteCallback callback) { @@ -6043,5 +6040,27 @@ public class AccountManagerService public boolean hasAccountAccess(@NonNull Account account, @IntRange(from = 0) int uid) { return AccountManagerService.this.hasAccountAccess(account, null, uid); } + + @Override + public byte[] backupAccountAccessPermissions(int userId) { + synchronized (mLock) { + if (mBackupHelper == null) { + mBackupHelper = new AccountManagerBackupHelper( + AccountManagerService.this, this); + } + return mBackupHelper.backupAccountAccessPermissions(userId); + } + } + + @Override + public void restoreAccountAccessPermissions(byte[] data, int userId) { + synchronized (mLock) { + if (mBackupHelper == null) { + mBackupHelper = new AccountManagerBackupHelper( + AccountManagerService.this, this); + } + mBackupHelper.restoreAccountAccessPermissions(data, userId); + } + } } } -- 2.11.0