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

Commit 4744319f authored by Dmitry Dementyev's avatar Dmitry Dementyev
Browse files

Lazely initialize AccountManager debug table.

Prevent crash if the database cannot be opened.
Guard not thread safe SQLiteStatement.

Change-Id: I2af9074e50bddc5a1d18ea781cc6f56934f21302
Test: atest com.android.server.accounts
Bug: 31708085
parent 2d8edbc9
Loading
Loading
Loading
Loading
+28 −37
Original line number Original line Diff line number Diff line
@@ -241,9 +241,6 @@ public class AccountManagerService
        private final HashMap<Account, AtomicReference<String>> previousNameCache =
        private final HashMap<Account, AtomicReference<String>> previousNameCache =
                new HashMap<Account, AtomicReference<String>>();
                new HashMap<Account, AtomicReference<String>>();


        private int debugDbInsertionPoint = -1;
        private SQLiteStatement statementForLogging; // TODO Move to AccountsDb

        UserAccounts(Context context, int userId, File preNDbFile, File deDbFile) {
        UserAccounts(Context context, int userId, File preNDbFile, File deDbFile) {
            this.userId = userId;
            this.userId = userId;
            synchronized (dbLock) {
            synchronized (dbLock) {
@@ -1299,7 +1296,6 @@ public class AccountManagerService
                File preNDbFile = new File(mInjector.getPreNDatabaseName(userId));
                File preNDbFile = new File(mInjector.getPreNDatabaseName(userId));
                File deDbFile = new File(mInjector.getDeDatabaseName(userId));
                File deDbFile = new File(mInjector.getDeDatabaseName(userId));
                accounts = new UserAccounts(mContext, userId, preNDbFile, deDbFile);
                accounts = new UserAccounts(mContext, userId, preNDbFile, deDbFile);
                initializeDebugDbSizeAndCompileSqlStatementForLogging(accounts);
                mUsers.append(userId, accounts);
                mUsers.append(userId, accounts);
                purgeOldGrants(accounts);
                purgeOldGrants(accounts);
                validateAccounts = true;
                validateAccounts = true;
@@ -1400,7 +1396,7 @@ public class AccountManagerService
        if (accounts != null) {
        if (accounts != null) {
            synchronized (accounts.dbLock) {
            synchronized (accounts.dbLock) {
                synchronized (accounts.cacheLock) {
                synchronized (accounts.cacheLock) {
                    accounts.statementForLogging.close();
                    accounts.accountsDb.closeDebugStatement();
                    accounts.accountsDb.close();
                    accounts.accountsDb.close();
                }
                }
            }
            }
@@ -5124,7 +5120,11 @@ public class AccountManagerService


            @Override
            @Override
            public void run() {
            public void run() {
                SQLiteStatement logStatement = userAccount.statementForLogging;
                synchronized (userAccount.accountsDb.mDebugStatementLock) {
                    SQLiteStatement logStatement = userAccount.accountsDb.getStatementForLogging();
                    if (logStatement == null) {
                        return; // Can't log.
                    }
                    logStatement.bindLong(1, accountId);
                    logStatement.bindLong(1, accountId);
                    logStatement.bindString(2, action);
                    logStatement.bindString(2, action);
                    logStatement.bindString(3, mDateFormat.format(new Date()));
                    logStatement.bindString(3, mDateFormat.format(new Date()));
@@ -5143,22 +5143,13 @@ public class AccountManagerService
                    }
                    }
                }
                }
            }
            }

        }
        long insertionPoint = userAccount.accountsDb.reserveDebugDbInsertionPoint();
        if (insertionPoint != -1) {
            LogRecordTask logTask = new LogRecordTask(action, tableName, accountId, userAccount,
            LogRecordTask logTask = new LogRecordTask(action, tableName, accountId, userAccount,
                callingUid, userAccount.debugDbInsertionPoint);
                    callingUid, insertionPoint);
        userAccount.debugDbInsertionPoint = (userAccount.debugDbInsertionPoint + 1)
                % AccountsDb.MAX_DEBUG_DB_SIZE;
            mHandler.post(logTask);
            mHandler.post(logTask);
        }
        }

    /*
     * This should only be called once to compile the sql statement for logging
     * and to find the insertion point.
     */
    private void initializeDebugDbSizeAndCompileSqlStatementForLogging(UserAccounts userAccount) {
        userAccount.debugDbInsertionPoint = userAccount.accountsDb
                .calculateDebugTableInsertionPoint();
        userAccount.statementForLogging = userAccount.accountsDb.compileSqlStatementForLogging();
    }
    }


    public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
    public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
+62 −15
Original line number Original line Diff line number Diff line
@@ -17,11 +17,13 @@
package com.android.server.accounts;
package com.android.server.accounts;


import android.accounts.Account;
import android.accounts.Account;
import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.ContentValues;
import android.content.Context;
import android.content.Context;
import android.database.Cursor;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteStatement;
import android.os.FileUtils;
import android.os.FileUtils;
@@ -183,6 +185,10 @@ class AccountsDb implements AutoCloseable {
    private final Context mContext;
    private final Context mContext;
    private final File mPreNDatabaseFile;
    private final File mPreNDatabaseFile;


    final Object mDebugStatementLock = new Object();
    private volatile long mDebugDbInsertionPoint = -1;
    private volatile SQLiteStatement mDebugStatementForLogging; // not thread safe.

    AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile) {
    AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile) {
        mDeDatabase = deDatabase;
        mDeDatabase = deDatabase;
        mContext = context;
        mContext = context;
@@ -1278,7 +1284,8 @@ class AccountsDb implements AutoCloseable {
     * Finds the row key where the next insertion should take place. Returns number of rows
     * Finds the row key where the next insertion should take place. Returns number of rows
     * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
     * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
     */
     */
    int calculateDebugTableInsertionPoint() {
    long calculateDebugTableInsertionPoint() {
        try {
            SQLiteDatabase db = mDeDatabase.getReadableDatabase();
            SQLiteDatabase db = mDeDatabase.getReadableDatabase();
            String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
            String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
            int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
            int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
@@ -1288,21 +1295,61 @@ class AccountsDb implements AutoCloseable {


            // This query finds the smallest timestamp value (and if 2 records have
            // This query finds the smallest timestamp value (and if 2 records have
            // same timestamp, the choose the lower id).
            // same timestamp, the choose the lower id).
        queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY +
            queryCountDebugDbRows =
                " FROM " + TABLE_DEBUG +
                    "SELECT " + DEBUG_TABLE_KEY
                " ORDER BY "  + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY +
                    + " FROM " + TABLE_DEBUG
                " LIMIT 1";
                    + " ORDER BY "  + DEBUG_TABLE_TIMESTAMP + ","
        return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
                    + DEBUG_TABLE_KEY
                    + " LIMIT 1";
            return DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
        } catch (SQLiteException e) {
            Log.e(TAG, "Failed to open debug table" + e);
            return -1;
        }
    }
    }


    SQLiteStatement compileSqlStatementForLogging() {
    SQLiteStatement compileSqlStatementForLogging() {
        // TODO b/31708085 Fix debug logging - it eagerly opens database for write without a need
        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
        String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
        String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
                + " VALUES (?,?,?,?,?,?)";
                + " VALUES (?,?,?,?,?,?)";
        return db.compileStatement(sql);
        return db.compileStatement(sql);
    }
    }


    /**
     * Returns statement for logging or {@code null} on database open failure.
     * Returned value must be guarded by {link #debugStatementLock}
     */
    @Nullable SQLiteStatement getStatementForLogging() {
        if (mDebugStatementForLogging != null) {
            return mDebugStatementForLogging;
        }
        try {
            mDebugStatementForLogging =  compileSqlStatementForLogging();
            return mDebugStatementForLogging;
        } catch (SQLiteException e) {
            Log.e(TAG, "Failed to open debug table" + e);
            return null;
        }
    }

    void closeDebugStatement() {
        synchronized (mDebugStatementLock) {
            if (mDebugStatementForLogging != null) {
                mDebugStatementForLogging.close();
                mDebugStatementForLogging = null;
            }
        }
    }

    long reserveDebugDbInsertionPoint() {
        if (mDebugDbInsertionPoint == -1) {
            mDebugDbInsertionPoint = calculateDebugTableInsertionPoint();
            return mDebugDbInsertionPoint;
        }
        mDebugDbInsertionPoint = (mDebugDbInsertionPoint + 1) % MAX_DEBUG_DB_SIZE;
        return mDebugDbInsertionPoint;
    }

    void dumpDebugTable(PrintWriter pw) {
    void dumpDebugTable(PrintWriter pw) {
        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
        Cursor cursor = db.query(TABLE_DEBUG, null,
        Cursor cursor = db.query(TABLE_DEBUG, null,
+41 −0
Original line number Original line Diff line number Diff line
@@ -22,9 +22,14 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;


import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.accounts.Account;
import android.accounts.Account;
import android.content.Context;
import android.content.Context;
import android.database.Cursor;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import android.os.Build;
import android.os.Build;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import android.util.Pair;
@@ -37,7 +42,11 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.RunWith;


import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;
import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Arrays;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
@@ -64,8 +73,11 @@ public class AccountsDbTest {
    private File deDb;
    private File deDb;
    private File ceDb;
    private File ceDb;


    @Mock private PrintWriter mockWriter;

    @Before
    @Before
    public void setUp() {
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        Context context = InstrumentationRegistry.getContext();
        Context context = InstrumentationRegistry.getContext();
        preNDb = new File(context.getCacheDir(), PREN_DB);
        preNDb = new File(context.getCacheDir(), PREN_DB);
        ceDb = new File(context.getCacheDir(), CE_DB);
        ceDb = new File(context.getCacheDir(), CE_DB);
@@ -444,4 +456,33 @@ public class AccountsDbTest {
        assertTrue(mAccountsDb.deleteDeAccount(accId)); // Trigger should remove visibility.
        assertTrue(mAccountsDb.deleteDeAccount(accId)); // Trigger should remove visibility.
        assertNull(mAccountsDb.findAccountVisibility(account, packageName1));
        assertNull(mAccountsDb.findAccountVisibility(account, packageName1));
    }
    }

    @Test
    public void testDumpDebugTable() {
        long accId = 10;
        long insertionPoint = mAccountsDb.reserveDebugDbInsertionPoint();

        SQLiteStatement logStatement = mAccountsDb.getStatementForLogging();

        logStatement.bindLong(1, accId);
        logStatement.bindString(2, "action");
        logStatement.bindString(3, "date");
        logStatement.bindLong(4, 10);
        logStatement.bindString(5, "table");
        logStatement.bindLong(6, insertionPoint);
        logStatement.execute();

        mAccountsDb.dumpDebugTable(mockWriter);

        verify(mockWriter, times(3)).println(anyString());
    }

    @Test
    public void testReserveDebugDbInsertionPoint() {
        long insertionPoint = mAccountsDb.reserveDebugDbInsertionPoint();
        long insertionPoint2 = mAccountsDb.reserveDebugDbInsertionPoint();

        assertEquals(0, insertionPoint);
        assertEquals(1, insertionPoint2);
    }
}
}