Loading services/core/java/com/android/server/accounts/AccountManagerService.java +91 −80 Original line number Diff line number Diff line Loading @@ -77,7 +77,6 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; Loading Loading @@ -152,7 +151,7 @@ public class AccountManagerService @Override public void onStart() { mService = new AccountManagerService(getContext()); mService = new AccountManagerService(new Injector(getContext())); publishBinderService(Context.ACCOUNT_SERVICE, mService); } Loading @@ -169,6 +168,7 @@ public class AccountManagerService private final PackageManager mPackageManager; private final AppOpsManager mAppOpsManager; private UserManager mUserManager; private final Injector mInjector; final MessageHandler mHandler; Loading Loading @@ -272,22 +272,13 @@ public class AccountManagerService return sThis.get(); } public AccountManagerService(Context context) { this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); } public AccountManagerService(Context context, PackageManager packageManager, IAccountAuthenticatorCache authenticatorCache) { mContext = context; mPackageManager = packageManager; public AccountManagerService(Injector injector) { mInjector = injector; mContext = injector.getContext(); mPackageManager = mContext.getPackageManager(); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); serviceThread.start(); mHandler = new MessageHandler(serviceThread.getLooper()); mAuthenticatorCache = authenticatorCache; mHandler = new MessageHandler(injector.getMessageHandlerLooper()); mAuthenticatorCache = mInjector.getAccountAuthenticatorCache(); mAuthenticatorCache.setListener(this, null /* Handler */); sThis.set(this); Loading Loading @@ -373,7 +364,7 @@ public class AccountManagerService } }, UserHandle.ALL, userFilter, null, null); LocalServices.addService(AccountManagerInternal.class, new AccountManagerInternalImpl()); injector.addLocalService(new AccountManagerInternalImpl()); // Need to cancel account request notifications if the update/install can access the account new PackageMonitor() { Loading Loading @@ -424,7 +415,7 @@ public class AccountManagerService final long identity = Binder.clearCallingIdentity(); try { for (String packageName : packageNames) { if (mContext.getPackageManager().checkPermission( if (mPackageManager.checkPermission( Manifest.permission.GET_ACCOUNTS, packageName) != PackageManager.PERMISSION_GRANTED) { continue; Loading Loading @@ -710,8 +701,7 @@ public class AccountManagerService * installed on the device. */ private void addRequestsForPreInstalledApplications() { List<PackageInfo> allInstalledPackages = mContext.getPackageManager(). getInstalledPackages(0); List<PackageInfo> allInstalledPackages = mPackageManager.getInstalledPackages(0); for(PackageInfo pi : allInstalledPackages) { int currentUid = pi.applicationInfo.uid; if(currentUid != -1) { Loading Loading @@ -1170,8 +1160,8 @@ public class AccountManagerService UserAccounts accounts = mUsers.get(userId); boolean validateAccounts = false; if (accounts == null) { File preNDbFile = new File(getPreNDatabaseName(userId)); File deDbFile = new File(getDeDatabaseName(userId)); File preNDbFile = new File(mInjector.getPreNDatabaseName(userId)); File deDbFile = new File(mInjector.getDeDatabaseName(userId)); accounts = new UserAccounts(mContext, userId, preNDbFile, deDbFile); initializeDebugDbSizeAndCompileSqlStatementForLogging( accounts.openHelper.getWritableDatabase(), accounts); Loading @@ -1183,8 +1173,8 @@ public class AccountManagerService if (!accounts.openHelper.isCeDatabaseAttached() && mLocalUnlockedUsers.get(userId)) { Log.i(TAG, "User " + userId + " is unlocked - opening CE database"); synchronized (accounts.cacheLock) { File preNDatabaseFile = new File(getPreNDatabaseName(userId)); File ceDatabaseFile = new File(getCeDatabaseName(userId)); File preNDatabaseFile = new File(mInjector.getPreNDatabaseName(userId)); File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId)); CeDatabaseHelper.create(mContext, userId, preNDatabaseFile, ceDatabaseFile); accounts.openHelper.attachCeDatabase(ceDatabaseFile); } Loading Loading @@ -1255,13 +1245,13 @@ public class AccountManagerService } } Log.i(TAG, "Removing database files for user " + userId); File dbFile = new File(getDeDatabaseName(userId)); File dbFile = new File(mInjector.getDeDatabaseName(userId)); AccountsDb.deleteDbFileWarnIfFailed(dbFile); // Remove CE file if user is unlocked, or FBE is not enabled boolean fbeEnabled = StorageManager.isFileEncryptedNativeOrEmulated(); if (!fbeEnabled || userUnlocked) { File ceDb = new File(getCeDatabaseName(userId)); File ceDb = new File(mInjector.getCeDatabaseName(userId)); if (ceDb.exists()) { AccountsDb.deleteDbFileWarnIfFailed(ceDb); } Loading Loading @@ -4745,47 +4735,6 @@ public class AccountManagerService } } @VisibleForTesting String getPreNDatabaseName(int userId) { File systemDir = Environment.getDataSystemDirectory(); File databaseFile = new File(Environment.getUserSystemDirectory(userId), PRE_N_DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. // Make sure the new file doesn't already exist. A dummy file could have been // accidentally created in the old location, causing the new one to become corrupted // as well. File oldFile = new File(systemDir, PRE_N_DATABASE_NAME); if (oldFile.exists() && !databaseFile.exists()) { // Check for use directory; create if it doesn't exist, else renameTo will fail File userDir = Environment.getUserSystemDirectory(userId); if (!userDir.exists()) { if (!userDir.mkdirs()) { throw new IllegalStateException("User dir cannot be created: " + userDir); } } if (!oldFile.renameTo(databaseFile)) { throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); } } } return databaseFile.getPath(); } @VisibleForTesting String getDeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemDeDirectory(userId), AccountsDb.DE_DATABASE_NAME); return databaseFile.getPath(); } @VisibleForTesting String getCeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemCeDirectory(userId), AccountsDb.CE_DATABASE_NAME); return databaseFile.getPath(); } private void logRecord(UserAccounts accounts, String action, String tableName) { logRecord(action, tableName, -1, accounts); } Loading Loading @@ -4981,17 +4930,11 @@ public class AccountManagerService } } @VisibleForTesting protected void installNotification(int notificationId, final Notification notification, UserHandle user) { installNotification(notificationId, notification, "android", user.getIdentifier()); } private void installNotification(int notificationId, final Notification notification, String packageName, int userId) { final long token = clearCallingIdentity(); try { INotificationManager notificationManager = NotificationManager.getService(); INotificationManager notificationManager = mInjector.getNotificationManager(); try { notificationManager.enqueueNotificationWithTag(packageName, packageName, null, notificationId, notification, new int[1], userId); Loading @@ -5003,16 +4946,14 @@ public class AccountManagerService } } @VisibleForTesting protected void cancelNotification(int id, UserHandle user) { private void cancelNotification(int id, UserHandle user) { cancelNotification(id, mContext.getPackageName(), user); } protected void cancelNotification(int id, String packageName, UserHandle user) { private void cancelNotification(int id, String packageName, UserHandle user) { long identityToken = clearCallingIdentity(); try { INotificationManager service = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); INotificationManager service = mInjector.getNotificationManager(); service.cancelNotificationWithTag(packageName, null, id, user.getIdentifier()); } catch (RemoteException e) { /* ignore - local call */ Loading Loading @@ -5714,4 +5655,74 @@ public class AccountManagerService } } } @VisibleForTesting static class Injector { private final Context mContext; public Injector(Context context) { mContext = context; } Looper getMessageHandlerLooper() { ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); serviceThread.start(); return serviceThread.getLooper(); } Context getContext() { return mContext; } void addLocalService(AccountManagerInternal service) { LocalServices.addService(AccountManagerInternal.class, service); } String getDeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemDeDirectory(userId), AccountsDb.DE_DATABASE_NAME); return databaseFile.getPath(); } String getCeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemCeDirectory(userId), AccountsDb.CE_DATABASE_NAME); return databaseFile.getPath(); } String getPreNDatabaseName(int userId) { File systemDir = Environment.getDataSystemDirectory(); File databaseFile = new File(Environment.getUserSystemDirectory(userId), PRE_N_DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. // Make sure the new file doesn't already exist. A dummy file could have been // accidentally created in the old location, causing the new one to become corrupted // as well. File oldFile = new File(systemDir, PRE_N_DATABASE_NAME); if (oldFile.exists() && !databaseFile.exists()) { // Check for use directory; create if it doesn't exist, else renameTo will fail File userDir = Environment.getUserSystemDirectory(userId); if (!userDir.exists()) { if (!userDir.mkdirs()) { throw new IllegalStateException("User dir cannot be created: " + userDir); } } if (!oldFile.renameTo(databaseFile)) { throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); } } } return databaseFile.getPath(); } IAccountAuthenticatorCache getAccountAuthenticatorCache() { return new AccountAuthenticatorCache(mContext); } INotificationManager getNotificationManager() { return NotificationManager.getService(); } } } services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +52 −45 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.accounts; import static android.database.sqlite.SQLiteDatabase.deleteDatabase; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading @@ -24,7 +26,7 @@ import android.accounts.Account; import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.app.AppOpsManager; import android.app.Notification; import android.app.INotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -38,16 +40,14 @@ import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.test.AndroidTestCase; import android.test.mock.MockContext; import android.test.mock.MockPackageManager; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; Loading @@ -55,29 +55,38 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class AccountManagerServiceTest extends AndroidTestCase { private static final String TAG = AccountManagerServiceTest.class.getSimpleName(); static final String PREN_DB = "pren.db"; static final String DE_DB = "de.db"; static final String CE_DB = "ce.db"; private static final String PREN_DB = "pren.db"; private static final String DE_DB = "de.db"; private static final String CE_DB = "ce.db"; private AccountManagerService mAms; private TestInjector mTestInjector; @Override protected void setUp() throws Exception { Context realTestContext = getContext(); Context mockContext = new MyMockContext(realTestContext); MyMockContext mockContext = new MyMockContext(realTestContext); setContext(mockContext); mAms = createAccountManagerService(mockContext, realTestContext); mTestInjector = new TestInjector(realTestContext, mockContext); mAms = new AccountManagerService(mTestInjector); } @Override protected void tearDown() throws Exception { SQLiteDatabase.deleteDatabase(new File(mAms.getCeDatabaseName(UserHandle.USER_SYSTEM))); SQLiteDatabase.deleteDatabase(new File(mAms.getDeDatabaseName(UserHandle.USER_SYSTEM))); SQLiteDatabase.deleteDatabase(new File(mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM))); LocalServices.removeServiceForTest(AccountManagerInternal.class); // Let async logging tasks finish, otherwise they may crash due to db being removed CountDownLatch cdl = new CountDownLatch(1); mAms.mHandler.post(() -> { deleteDatabase(new File(mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM))); deleteDatabase(new File(mTestInjector.getDeDatabaseName(UserHandle.USER_SYSTEM))); deleteDatabase(new File(mTestInjector.getPreNDatabaseName(UserHandle.USER_SYSTEM))); cdl.countDown(); }); cdl.await(1, TimeUnit.SECONDS); super.tearDown(); } Loading Loading @@ -230,7 +239,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { Context originalContext = ((MyMockContext)getContext()).mTestContext; // create a separate instance of AMS. It initially assumes that user0 is locked AccountManagerService ams2 = createAccountManagerService(getContext(), originalContext); AccountManagerService ams2 = new AccountManagerService(mTestInjector); // Verify that account can be removed when user is locked ams2.removeAccountInternal(a1); Loading @@ -239,7 +248,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertEquals("Only a2 should be returned", a2, accounts[0]); // Verify that CE db file is unchanged and still has 2 accounts String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM); String ceDatabaseName = mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM); int accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName); assertEquals("CE database should still have 2 accounts", 2, accountsNumber); Loading @@ -254,7 +263,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { @SmallTest public void testPreNDatabaseMigration() throws Exception { String preNDatabaseName = mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM); String preNDatabaseName = mTestInjector.getPreNDatabaseName(UserHandle.USER_SYSTEM); Context originalContext = ((MyMockContext) getContext()).mTestContext; PreNTestDatabaseHelper.createV4Database(originalContext, preNDatabaseName); // Assert that database was created with 1 account Loading @@ -275,8 +284,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { new File(preNDatabaseName).exists()); // Verify that ce/de files are present String deDatabaseName = mAms.getDeDatabaseName(UserHandle.USER_SYSTEM); String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM); String deDatabaseName = mTestInjector.getDeDatabaseName(UserHandle.USER_SYSTEM); String ceDatabaseName = mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM); assertTrue("DE database file should be created at " + deDatabaseName, new File(deDatabaseName).exists()); assertTrue("CE database file should be created at " + ceDatabaseName, Loading @@ -291,13 +300,6 @@ public class AccountManagerServiceTest extends AndroidTestCase { } } private AccountManagerService createAccountManagerService(Context mockContext, Context realContext) { LocalServices.removeServiceForTest(AccountManagerInternal.class); return new MyAccountManagerService(mockContext, new MyMockPackageManager(), new MockAccountAuthenticatorCache(), realContext); } private void unlockSystemUser() { mAms.onUserUnlocked(newIntentForUser(UserHandle.USER_SYSTEM)); } Loading Loading @@ -366,6 +368,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { this.mAppOpsManager = mock(AppOpsManager.class); this.mUserManager = mock(UserManager.class); this.mPackageManager = mock(PackageManager.class); when(mPackageManager.checkSignatures(anyInt(), anyInt())) .thenReturn(PackageManager.SIGNATURE_MATCH); final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0); when(mUserManager.getUserInfo(eq(ui.id))).thenReturn(ui); } Loading @@ -380,6 +384,11 @@ public class AccountManagerServiceTest extends AndroidTestCase { return mPackageManager; } @Override public String getPackageName() { return mTestContext.getPackageName(); } @Override public Object getSystemService(String name) { if (Context.APP_OPS_SERVICE.equals(name)) { Loading Loading @@ -427,47 +436,45 @@ public class AccountManagerServiceTest extends AndroidTestCase { } } static class MyMockPackageManager extends MockPackageManager { @Override public int checkSignatures(final int uid1, final int uid2) { return PackageManager.SIGNATURE_MATCH; static class TestInjector extends AccountManagerService.Injector { private Context mRealContext; TestInjector(Context realContext, MyMockContext mockContext) { super(mockContext); mRealContext = realContext; } @Override public void addOnPermissionsChangeListener( OnPermissionsChangedListener listener) { } } static class MyAccountManagerService extends AccountManagerService { private Context mRealTestContext; MyAccountManagerService(Context context, PackageManager packageManager, IAccountAuthenticatorCache authenticatorCache, Context realTestContext) { super(context, packageManager, authenticatorCache); this.mRealTestContext = realTestContext; Looper getMessageHandlerLooper() { return Looper.getMainLooper(); } @Override protected void installNotification(final int notificationId, final Notification n, UserHandle user) { void addLocalService(AccountManagerInternal service) { } @Override protected void cancelNotification(final int id, UserHandle user) { IAccountAuthenticatorCache getAccountAuthenticatorCache() { return new MockAccountAuthenticatorCache(); } @Override protected String getCeDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), CE_DB).getPath(); return new File(mRealContext.getCacheDir(), CE_DB).getPath(); } @Override protected String getDeDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), DE_DB).getPath(); return new File(mRealContext.getCacheDir(), DE_DB).getPath(); } @Override String getPreNDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), PREN_DB).getPath(); return new File(mRealContext.getCacheDir(), PREN_DB).getPath(); } @Override INotificationManager getNotificationManager() { return mock(INotificationManager.class); } } } Loading
services/core/java/com/android/server/accounts/AccountManagerService.java +91 −80 Original line number Diff line number Diff line Loading @@ -77,7 +77,6 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; Loading Loading @@ -152,7 +151,7 @@ public class AccountManagerService @Override public void onStart() { mService = new AccountManagerService(getContext()); mService = new AccountManagerService(new Injector(getContext())); publishBinderService(Context.ACCOUNT_SERVICE, mService); } Loading @@ -169,6 +168,7 @@ public class AccountManagerService private final PackageManager mPackageManager; private final AppOpsManager mAppOpsManager; private UserManager mUserManager; private final Injector mInjector; final MessageHandler mHandler; Loading Loading @@ -272,22 +272,13 @@ public class AccountManagerService return sThis.get(); } public AccountManagerService(Context context) { this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); } public AccountManagerService(Context context, PackageManager packageManager, IAccountAuthenticatorCache authenticatorCache) { mContext = context; mPackageManager = packageManager; public AccountManagerService(Injector injector) { mInjector = injector; mContext = injector.getContext(); mPackageManager = mContext.getPackageManager(); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); serviceThread.start(); mHandler = new MessageHandler(serviceThread.getLooper()); mAuthenticatorCache = authenticatorCache; mHandler = new MessageHandler(injector.getMessageHandlerLooper()); mAuthenticatorCache = mInjector.getAccountAuthenticatorCache(); mAuthenticatorCache.setListener(this, null /* Handler */); sThis.set(this); Loading Loading @@ -373,7 +364,7 @@ public class AccountManagerService } }, UserHandle.ALL, userFilter, null, null); LocalServices.addService(AccountManagerInternal.class, new AccountManagerInternalImpl()); injector.addLocalService(new AccountManagerInternalImpl()); // Need to cancel account request notifications if the update/install can access the account new PackageMonitor() { Loading Loading @@ -424,7 +415,7 @@ public class AccountManagerService final long identity = Binder.clearCallingIdentity(); try { for (String packageName : packageNames) { if (mContext.getPackageManager().checkPermission( if (mPackageManager.checkPermission( Manifest.permission.GET_ACCOUNTS, packageName) != PackageManager.PERMISSION_GRANTED) { continue; Loading Loading @@ -710,8 +701,7 @@ public class AccountManagerService * installed on the device. */ private void addRequestsForPreInstalledApplications() { List<PackageInfo> allInstalledPackages = mContext.getPackageManager(). getInstalledPackages(0); List<PackageInfo> allInstalledPackages = mPackageManager.getInstalledPackages(0); for(PackageInfo pi : allInstalledPackages) { int currentUid = pi.applicationInfo.uid; if(currentUid != -1) { Loading Loading @@ -1170,8 +1160,8 @@ public class AccountManagerService UserAccounts accounts = mUsers.get(userId); boolean validateAccounts = false; if (accounts == null) { File preNDbFile = new File(getPreNDatabaseName(userId)); File deDbFile = new File(getDeDatabaseName(userId)); File preNDbFile = new File(mInjector.getPreNDatabaseName(userId)); File deDbFile = new File(mInjector.getDeDatabaseName(userId)); accounts = new UserAccounts(mContext, userId, preNDbFile, deDbFile); initializeDebugDbSizeAndCompileSqlStatementForLogging( accounts.openHelper.getWritableDatabase(), accounts); Loading @@ -1183,8 +1173,8 @@ public class AccountManagerService if (!accounts.openHelper.isCeDatabaseAttached() && mLocalUnlockedUsers.get(userId)) { Log.i(TAG, "User " + userId + " is unlocked - opening CE database"); synchronized (accounts.cacheLock) { File preNDatabaseFile = new File(getPreNDatabaseName(userId)); File ceDatabaseFile = new File(getCeDatabaseName(userId)); File preNDatabaseFile = new File(mInjector.getPreNDatabaseName(userId)); File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId)); CeDatabaseHelper.create(mContext, userId, preNDatabaseFile, ceDatabaseFile); accounts.openHelper.attachCeDatabase(ceDatabaseFile); } Loading Loading @@ -1255,13 +1245,13 @@ public class AccountManagerService } } Log.i(TAG, "Removing database files for user " + userId); File dbFile = new File(getDeDatabaseName(userId)); File dbFile = new File(mInjector.getDeDatabaseName(userId)); AccountsDb.deleteDbFileWarnIfFailed(dbFile); // Remove CE file if user is unlocked, or FBE is not enabled boolean fbeEnabled = StorageManager.isFileEncryptedNativeOrEmulated(); if (!fbeEnabled || userUnlocked) { File ceDb = new File(getCeDatabaseName(userId)); File ceDb = new File(mInjector.getCeDatabaseName(userId)); if (ceDb.exists()) { AccountsDb.deleteDbFileWarnIfFailed(ceDb); } Loading Loading @@ -4745,47 +4735,6 @@ public class AccountManagerService } } @VisibleForTesting String getPreNDatabaseName(int userId) { File systemDir = Environment.getDataSystemDirectory(); File databaseFile = new File(Environment.getUserSystemDirectory(userId), PRE_N_DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. // Make sure the new file doesn't already exist. A dummy file could have been // accidentally created in the old location, causing the new one to become corrupted // as well. File oldFile = new File(systemDir, PRE_N_DATABASE_NAME); if (oldFile.exists() && !databaseFile.exists()) { // Check for use directory; create if it doesn't exist, else renameTo will fail File userDir = Environment.getUserSystemDirectory(userId); if (!userDir.exists()) { if (!userDir.mkdirs()) { throw new IllegalStateException("User dir cannot be created: " + userDir); } } if (!oldFile.renameTo(databaseFile)) { throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); } } } return databaseFile.getPath(); } @VisibleForTesting String getDeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemDeDirectory(userId), AccountsDb.DE_DATABASE_NAME); return databaseFile.getPath(); } @VisibleForTesting String getCeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemCeDirectory(userId), AccountsDb.CE_DATABASE_NAME); return databaseFile.getPath(); } private void logRecord(UserAccounts accounts, String action, String tableName) { logRecord(action, tableName, -1, accounts); } Loading Loading @@ -4981,17 +4930,11 @@ public class AccountManagerService } } @VisibleForTesting protected void installNotification(int notificationId, final Notification notification, UserHandle user) { installNotification(notificationId, notification, "android", user.getIdentifier()); } private void installNotification(int notificationId, final Notification notification, String packageName, int userId) { final long token = clearCallingIdentity(); try { INotificationManager notificationManager = NotificationManager.getService(); INotificationManager notificationManager = mInjector.getNotificationManager(); try { notificationManager.enqueueNotificationWithTag(packageName, packageName, null, notificationId, notification, new int[1], userId); Loading @@ -5003,16 +4946,14 @@ public class AccountManagerService } } @VisibleForTesting protected void cancelNotification(int id, UserHandle user) { private void cancelNotification(int id, UserHandle user) { cancelNotification(id, mContext.getPackageName(), user); } protected void cancelNotification(int id, String packageName, UserHandle user) { private void cancelNotification(int id, String packageName, UserHandle user) { long identityToken = clearCallingIdentity(); try { INotificationManager service = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); INotificationManager service = mInjector.getNotificationManager(); service.cancelNotificationWithTag(packageName, null, id, user.getIdentifier()); } catch (RemoteException e) { /* ignore - local call */ Loading Loading @@ -5714,4 +5655,74 @@ public class AccountManagerService } } } @VisibleForTesting static class Injector { private final Context mContext; public Injector(Context context) { mContext = context; } Looper getMessageHandlerLooper() { ServiceThread serviceThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); serviceThread.start(); return serviceThread.getLooper(); } Context getContext() { return mContext; } void addLocalService(AccountManagerInternal service) { LocalServices.addService(AccountManagerInternal.class, service); } String getDeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemDeDirectory(userId), AccountsDb.DE_DATABASE_NAME); return databaseFile.getPath(); } String getCeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemCeDirectory(userId), AccountsDb.CE_DATABASE_NAME); return databaseFile.getPath(); } String getPreNDatabaseName(int userId) { File systemDir = Environment.getDataSystemDirectory(); File databaseFile = new File(Environment.getUserSystemDirectory(userId), PRE_N_DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. // Make sure the new file doesn't already exist. A dummy file could have been // accidentally created in the old location, causing the new one to become corrupted // as well. File oldFile = new File(systemDir, PRE_N_DATABASE_NAME); if (oldFile.exists() && !databaseFile.exists()) { // Check for use directory; create if it doesn't exist, else renameTo will fail File userDir = Environment.getUserSystemDirectory(userId); if (!userDir.exists()) { if (!userDir.mkdirs()) { throw new IllegalStateException("User dir cannot be created: " + userDir); } } if (!oldFile.renameTo(databaseFile)) { throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); } } } return databaseFile.getPath(); } IAccountAuthenticatorCache getAccountAuthenticatorCache() { return new AccountAuthenticatorCache(mContext); } INotificationManager getNotificationManager() { return NotificationManager.getService(); } } }
services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +52 −45 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.accounts; import static android.database.sqlite.SQLiteDatabase.deleteDatabase; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading @@ -24,7 +26,7 @@ import android.accounts.Account; import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.app.AppOpsManager; import android.app.Notification; import android.app.INotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -38,16 +40,14 @@ import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.test.AndroidTestCase; import android.test.mock.MockContext; import android.test.mock.MockPackageManager; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; Loading @@ -55,29 +55,38 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class AccountManagerServiceTest extends AndroidTestCase { private static final String TAG = AccountManagerServiceTest.class.getSimpleName(); static final String PREN_DB = "pren.db"; static final String DE_DB = "de.db"; static final String CE_DB = "ce.db"; private static final String PREN_DB = "pren.db"; private static final String DE_DB = "de.db"; private static final String CE_DB = "ce.db"; private AccountManagerService mAms; private TestInjector mTestInjector; @Override protected void setUp() throws Exception { Context realTestContext = getContext(); Context mockContext = new MyMockContext(realTestContext); MyMockContext mockContext = new MyMockContext(realTestContext); setContext(mockContext); mAms = createAccountManagerService(mockContext, realTestContext); mTestInjector = new TestInjector(realTestContext, mockContext); mAms = new AccountManagerService(mTestInjector); } @Override protected void tearDown() throws Exception { SQLiteDatabase.deleteDatabase(new File(mAms.getCeDatabaseName(UserHandle.USER_SYSTEM))); SQLiteDatabase.deleteDatabase(new File(mAms.getDeDatabaseName(UserHandle.USER_SYSTEM))); SQLiteDatabase.deleteDatabase(new File(mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM))); LocalServices.removeServiceForTest(AccountManagerInternal.class); // Let async logging tasks finish, otherwise they may crash due to db being removed CountDownLatch cdl = new CountDownLatch(1); mAms.mHandler.post(() -> { deleteDatabase(new File(mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM))); deleteDatabase(new File(mTestInjector.getDeDatabaseName(UserHandle.USER_SYSTEM))); deleteDatabase(new File(mTestInjector.getPreNDatabaseName(UserHandle.USER_SYSTEM))); cdl.countDown(); }); cdl.await(1, TimeUnit.SECONDS); super.tearDown(); } Loading Loading @@ -230,7 +239,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { Context originalContext = ((MyMockContext)getContext()).mTestContext; // create a separate instance of AMS. It initially assumes that user0 is locked AccountManagerService ams2 = createAccountManagerService(getContext(), originalContext); AccountManagerService ams2 = new AccountManagerService(mTestInjector); // Verify that account can be removed when user is locked ams2.removeAccountInternal(a1); Loading @@ -239,7 +248,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertEquals("Only a2 should be returned", a2, accounts[0]); // Verify that CE db file is unchanged and still has 2 accounts String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM); String ceDatabaseName = mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM); int accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName); assertEquals("CE database should still have 2 accounts", 2, accountsNumber); Loading @@ -254,7 +263,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { @SmallTest public void testPreNDatabaseMigration() throws Exception { String preNDatabaseName = mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM); String preNDatabaseName = mTestInjector.getPreNDatabaseName(UserHandle.USER_SYSTEM); Context originalContext = ((MyMockContext) getContext()).mTestContext; PreNTestDatabaseHelper.createV4Database(originalContext, preNDatabaseName); // Assert that database was created with 1 account Loading @@ -275,8 +284,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { new File(preNDatabaseName).exists()); // Verify that ce/de files are present String deDatabaseName = mAms.getDeDatabaseName(UserHandle.USER_SYSTEM); String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM); String deDatabaseName = mTestInjector.getDeDatabaseName(UserHandle.USER_SYSTEM); String ceDatabaseName = mTestInjector.getCeDatabaseName(UserHandle.USER_SYSTEM); assertTrue("DE database file should be created at " + deDatabaseName, new File(deDatabaseName).exists()); assertTrue("CE database file should be created at " + ceDatabaseName, Loading @@ -291,13 +300,6 @@ public class AccountManagerServiceTest extends AndroidTestCase { } } private AccountManagerService createAccountManagerService(Context mockContext, Context realContext) { LocalServices.removeServiceForTest(AccountManagerInternal.class); return new MyAccountManagerService(mockContext, new MyMockPackageManager(), new MockAccountAuthenticatorCache(), realContext); } private void unlockSystemUser() { mAms.onUserUnlocked(newIntentForUser(UserHandle.USER_SYSTEM)); } Loading Loading @@ -366,6 +368,8 @@ public class AccountManagerServiceTest extends AndroidTestCase { this.mAppOpsManager = mock(AppOpsManager.class); this.mUserManager = mock(UserManager.class); this.mPackageManager = mock(PackageManager.class); when(mPackageManager.checkSignatures(anyInt(), anyInt())) .thenReturn(PackageManager.SIGNATURE_MATCH); final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0); when(mUserManager.getUserInfo(eq(ui.id))).thenReturn(ui); } Loading @@ -380,6 +384,11 @@ public class AccountManagerServiceTest extends AndroidTestCase { return mPackageManager; } @Override public String getPackageName() { return mTestContext.getPackageName(); } @Override public Object getSystemService(String name) { if (Context.APP_OPS_SERVICE.equals(name)) { Loading Loading @@ -427,47 +436,45 @@ public class AccountManagerServiceTest extends AndroidTestCase { } } static class MyMockPackageManager extends MockPackageManager { @Override public int checkSignatures(final int uid1, final int uid2) { return PackageManager.SIGNATURE_MATCH; static class TestInjector extends AccountManagerService.Injector { private Context mRealContext; TestInjector(Context realContext, MyMockContext mockContext) { super(mockContext); mRealContext = realContext; } @Override public void addOnPermissionsChangeListener( OnPermissionsChangedListener listener) { } } static class MyAccountManagerService extends AccountManagerService { private Context mRealTestContext; MyAccountManagerService(Context context, PackageManager packageManager, IAccountAuthenticatorCache authenticatorCache, Context realTestContext) { super(context, packageManager, authenticatorCache); this.mRealTestContext = realTestContext; Looper getMessageHandlerLooper() { return Looper.getMainLooper(); } @Override protected void installNotification(final int notificationId, final Notification n, UserHandle user) { void addLocalService(AccountManagerInternal service) { } @Override protected void cancelNotification(final int id, UserHandle user) { IAccountAuthenticatorCache getAccountAuthenticatorCache() { return new MockAccountAuthenticatorCache(); } @Override protected String getCeDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), CE_DB).getPath(); return new File(mRealContext.getCacheDir(), CE_DB).getPath(); } @Override protected String getDeDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), DE_DB).getPath(); return new File(mRealContext.getCacheDir(), DE_DB).getPath(); } @Override String getPreNDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), PREN_DB).getPath(); return new File(mRealContext.getCacheDir(), PREN_DB).getPath(); } @Override INotificationManager getNotificationManager() { return mock(INotificationManager.class); } } }