/* * Copyright (C) 2009 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 static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.accounts.Account; import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.app.AppOpsManager; import android.app.Notification; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.RegisteredServicesCache.ServiceInfo; import android.content.pm.RegisteredServicesCacheListener; import android.content.pm.UserInfo; import android.database.Cursor; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.test.AndroidTestCase; import android.test.mock.MockContext; import android.test.mock.MockPackageManager; import android.util.Log; import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; 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 AccountManagerService mAms; @Override protected void setUp() throws Exception { Context realTestContext = getContext(); Context mockContext = new MyMockContext(realTestContext); setContext(mockContext); mAms = createAccountManagerService(mockContext, realTestContext); } @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); super.tearDown(); } public class AccountSorter implements Comparator<Account> { public int compare(Account object1, Account object2) { if (object1 == object2) return 0; if (object1 == null) return 1; if (object2 == null) return -1; int result = object1.type.compareTo(object2.type); if (result != 0) return result; return object1.name.compareTo(object2.name); } } public void testCheckAddAccount() throws Exception { unlockSystemUser(); Account a11 = new Account("account1", "type1"); Account a21 = new Account("account2", "type1"); Account a31 = new Account("account3", "type1"); Account a12 = new Account("account1", "type2"); Account a22 = new Account("account2", "type2"); Account a32 = new Account("account3", "type2"); mAms.addAccountExplicitly(a11, "p11", null); mAms.addAccountExplicitly(a12, "p12", null); mAms.addAccountExplicitly(a21, "p21", null); mAms.addAccountExplicitly(a22, "p22", null); mAms.addAccountExplicitly(a31, "p31", null); mAms.addAccountExplicitly(a32, "p32", null); Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(6, accounts.length); assertEquals(a11, accounts[0]); assertEquals(a21, accounts[1]); assertEquals(a31, accounts[2]); assertEquals(a12, accounts[3]); assertEquals(a22, accounts[4]); assertEquals(a32, accounts[5]); accounts = mAms.getAccounts("type1", mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(3, accounts.length); assertEquals(a11, accounts[0]); assertEquals(a21, accounts[1]); assertEquals(a31, accounts[2]); mAms.removeAccountInternal(a21); accounts = mAms.getAccounts("type1", mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(2, accounts.length); assertEquals(a11, accounts[0]); assertEquals(a31, accounts[1]); } public void testPasswords() throws Exception { unlockSystemUser(); Account a11 = new Account("account1", "type1"); Account a12 = new Account("account1", "type2"); mAms.addAccountExplicitly(a11, "p11", null); mAms.addAccountExplicitly(a12, "p12", null); assertEquals("p11", mAms.getPassword(a11)); assertEquals("p12", mAms.getPassword(a12)); mAms.setPassword(a11, "p11b"); assertEquals("p11b", mAms.getPassword(a11)); assertEquals("p12", mAms.getPassword(a12)); } public void testUserdata() throws Exception { unlockSystemUser(); Account a11 = new Account("account1", "type1"); Bundle u11 = new Bundle(); u11.putString("a", "a_a11"); u11.putString("b", "b_a11"); u11.putString("c", "c_a11"); Account a12 = new Account("account1", "type2"); Bundle u12 = new Bundle(); u12.putString("a", "a_a12"); u12.putString("b", "b_a12"); u12.putString("c", "c_a12"); mAms.addAccountExplicitly(a11, "p11", u11); mAms.addAccountExplicitly(a12, "p12", u12); assertEquals("a_a11", mAms.getUserData(a11, "a")); assertEquals("b_a11", mAms.getUserData(a11, "b")); assertEquals("c_a11", mAms.getUserData(a11, "c")); assertEquals("a_a12", mAms.getUserData(a12, "a")); assertEquals("b_a12", mAms.getUserData(a12, "b")); assertEquals("c_a12", mAms.getUserData(a12, "c")); mAms.setUserData(a11, "b", "b_a11b"); mAms.setUserData(a12, "c", null); assertEquals("a_a11", mAms.getUserData(a11, "a")); assertEquals("b_a11b", mAms.getUserData(a11, "b")); assertEquals("c_a11", mAms.getUserData(a11, "c")); assertEquals("a_a12", mAms.getUserData(a12, "a")); assertEquals("b_a12", mAms.getUserData(a12, "b")); assertNull(mAms.getUserData(a12, "c")); } public void testAuthtokens() throws Exception { unlockSystemUser(); Account a11 = new Account("account1", "type1"); Account a12 = new Account("account1", "type2"); mAms.addAccountExplicitly(a11, "p11", null); mAms.addAccountExplicitly(a12, "p12", null); mAms.setAuthToken(a11, "att1", "a11_att1"); mAms.setAuthToken(a11, "att2", "a11_att2"); mAms.setAuthToken(a11, "att3", "a11_att3"); mAms.setAuthToken(a12, "att1", "a12_att1"); mAms.setAuthToken(a12, "att2", "a12_att2"); mAms.setAuthToken(a12, "att3", "a12_att3"); assertEquals("a11_att1", mAms.peekAuthToken(a11, "att1")); assertEquals("a11_att2", mAms.peekAuthToken(a11, "att2")); assertEquals("a11_att3", mAms.peekAuthToken(a11, "att3")); assertEquals("a12_att1", mAms.peekAuthToken(a12, "att1")); assertEquals("a12_att2", mAms.peekAuthToken(a12, "att2")); assertEquals("a12_att3", mAms.peekAuthToken(a12, "att3")); mAms.setAuthToken(a11, "att3", "a11_att3b"); mAms.invalidateAuthToken(a12.type, "a12_att2"); assertEquals("a11_att1", mAms.peekAuthToken(a11, "att1")); assertEquals("a11_att2", mAms.peekAuthToken(a11, "att2")); assertEquals("a11_att3b", mAms.peekAuthToken(a11, "att3")); assertEquals("a12_att1", mAms.peekAuthToken(a12, "att1")); assertNull(mAms.peekAuthToken(a12, "att2")); assertEquals("a12_att3", mAms.peekAuthToken(a12, "att3")); assertNull(mAms.peekAuthToken(a12, "att2")); } public void testRemovedAccountSync() throws Exception { unlockSystemUser(); Account a1 = new Account("account1", "type1"); Account a2 = new Account("account2", "type2"); mAms.addAccountExplicitly(a1, "p1", null); mAms.addAccountExplicitly(a2, "p2", null); Context originalContext = ((MyMockContext)getContext()).mTestContext; // create a separate instance of AMS. It initially assumes that user0 is locked AccountManagerService ams2 = createAccountManagerService(getContext(), originalContext); // Verify that account can be removed when user is locked ams2.removeAccountInternal(a1); Account[] accounts = ams2.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName()); assertEquals(1, accounts.length); 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); int accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName); assertEquals("CE database should still have 2 accounts", 2, accountsNumber); // Unlock the user and verify that db has been updated ams2.onUserUnlocked(newIntentForUser(UserHandle.USER_SYSTEM)); accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName); assertEquals("CE database should now have 1 account", 2, accountsNumber); accounts = ams2.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName()); assertEquals(1, accounts.length); assertEquals("Only a2 should be returned", a2, accounts[0]); } public void testPreNDatabaseMigration() throws Exception { String preNDatabaseName = mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM); Context originalContext = ((MyMockContext) getContext()).mTestContext; PreNTestDatabaseHelper.createV4Database(originalContext, preNDatabaseName); // Assert that database was created with 1 account int n = readNumberOfAccountsFromDbFile(originalContext, preNDatabaseName); assertEquals("pre-N database should have 1 account", 1, n); // Start testing unlockSystemUser(); Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); assertEquals("1 account should be migrated", 1, accounts.length); assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name); assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0])); assertEquals("Authtoken should be migrated", PreNTestDatabaseHelper.TOKEN_STRING, mAms.peekAuthToken(accounts[0], PreNTestDatabaseHelper.TOKEN_TYPE)); assertFalse("pre-N database file should be removed but was found at " + preNDatabaseName, 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); assertTrue("DE database file should be created at " + deDatabaseName, new File(deDatabaseName).exists()); assertTrue("CE database file should be created at " + ceDatabaseName, new File(ceDatabaseName).exists()); } private int readNumberOfAccountsFromDbFile(Context context, String dbName) { SQLiteDatabase ceDb = context.openOrCreateDatabase(dbName, 0, null); try (Cursor cursor = ceDb.rawQuery("SELECT count(*) FROM accounts", null)) { assertTrue(cursor.moveToNext()); return cursor.getInt(0); } } 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)); } private static Intent newIntentForUser(int userId) { Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); return intent; } static public class MockAccountAuthenticatorCache implements IAccountAuthenticatorCache { private ArrayList<ServiceInfo<AuthenticatorDescription>> mServices; public MockAccountAuthenticatorCache() { mServices = new ArrayList<>(); AuthenticatorDescription d1 = new AuthenticatorDescription("type1", "p1", 0, 0, 0, 0); AuthenticatorDescription d2 = new AuthenticatorDescription("type2", "p2", 0, 0, 0, 0); mServices.add(new ServiceInfo<>(d1, null, null)); mServices.add(new ServiceInfo<>(d2, null, null)); } @Override public ServiceInfo<AuthenticatorDescription> getServiceInfo( AuthenticatorDescription type, int userId) { for (ServiceInfo<AuthenticatorDescription> service : mServices) { if (service.type.equals(type)) { return service; } } return null; } @Override public Collection<ServiceInfo<AuthenticatorDescription>> getAllServices(int userId) { return mServices; } @Override public void dump( final FileDescriptor fd, final PrintWriter fout, final String[] args, int userId) { } @Override public void setListener( final RegisteredServicesCacheListener<AuthenticatorDescription> listener, final Handler handler) { } @Override public void invalidateCache(int userId) { } @Override public void updateServices(int userId) { } } static public class MyMockContext extends MockContext { private Context mTestContext; private AppOpsManager mAppOpsManager; private UserManager mUserManager; private PackageManager mPackageManager; public MyMockContext(Context testContext) { this.mTestContext = testContext; this.mAppOpsManager = mock(AppOpsManager.class); this.mUserManager = mock(UserManager.class); this.mPackageManager = mock(PackageManager.class); final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0); when(mUserManager.getUserInfo(eq(ui.id))).thenReturn(ui); } @Override public int checkCallingOrSelfPermission(final String permission) { return PackageManager.PERMISSION_GRANTED; } @Override public PackageManager getPackageManager() { return mPackageManager; } @Override public Object getSystemService(String name) { if (Context.APP_OPS_SERVICE.equals(name)) { return mAppOpsManager; } else if( Context.USER_SERVICE.equals(name)) { return mUserManager; } return null; } @Override public String getSystemServiceName(Class<?> serviceClass) { if (AppOpsManager.class.equals(serviceClass)) { return Context.APP_OPS_SERVICE; } return null; } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { return null; } @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { return null; } @Override public SQLiteDatabase openOrCreateDatabase(String file, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { Log.i(TAG, "openOrCreateDatabase " + file + " mode " + mode); return mTestContext.openOrCreateDatabase(file, mode, factory,errorHandler); } @Override public void sendBroadcastAsUser(Intent intent, UserHandle user) { Log.i(TAG, "sendBroadcastAsUser " + intent + " " + user); } @Override public String getOpPackageName() { return null; } } static public class MyMockPackageManager extends MockPackageManager { @Override public int checkSignatures(final int uid1, final int uid2) { return PackageManager.SIGNATURE_MATCH; } @Override public void addOnPermissionsChangeListener( OnPermissionsChangedListener listener) { } } static public class MyAccountManagerService extends AccountManagerService { private Context mRealTestContext; public MyAccountManagerService(Context context, PackageManager packageManager, IAccountAuthenticatorCache authenticatorCache, Context realTestContext) { super(context, packageManager, authenticatorCache); this.mRealTestContext = realTestContext; } @Override protected void installNotification(final int notificationId, final Notification n, UserHandle user) { } @Override protected void cancelNotification(final int id, UserHandle user) { } @Override protected String getCeDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), CE_DB).getPath(); } @Override protected String getDeDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), DE_DB).getPath(); } @Override String getPreNDatabaseName(int userId) { return new File(mRealTestContext.getCacheDir(), PREN_DB).getPath(); } } }