Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit da993806 authored by Fyodor Kupolov's avatar Fyodor Kupolov
Browse files

Added dependency injection for testing

Also fixed an issue when tests occasionally were crashing on APCT.
Now tearDown waits for async logging tasks to complete before
removing db files.

Test: AMS tests are passing

Bug: 31630010
Change-Id: I98f7b0899e54f4927b493e36d16dd946b9ca13a9
parent e1b15e21
Loading
Loading
Loading
Loading
+91 −80
Original line number Diff line number Diff line
@@ -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;
@@ -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);
        }

@@ -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;

@@ -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);
@@ -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() {
@@ -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;
@@ -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) {
@@ -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);
@@ -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);
                }
@@ -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);
            }
@@ -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);
    }
@@ -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);
@@ -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 */
@@ -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();
        }
    }
}
+52 −45
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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();
    }

@@ -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);
@@ -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);

@@ -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
@@ -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,
@@ -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));
    }
@@ -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);
        }
@@ -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)) {
@@ -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);
        }
    }
}