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

Commit a44febc6 authored by Danning Chen's avatar Danning Chen
Browse files

Run PeopleService.onUserUnlocked() on worker thread instead of main thread

This change is targeting on reducing the latency to unlock a user. It
includes these changes:
(1) In DataManager.onUserUnlocked(), execute the listeners registration
code on a worker thread.
(2) Merge the usage stats service query and data persistence to one thread.
(3) Adjust the query events max age from 1 day to 5 mins because we
already persist those events in PeopleService. No need to re-query those
events.

With this change, PeopleService.onUserUnlocked() execution is reduced
from ~200 ms to <1ms. And listeners are set up after conversations being
loaded from the disk. This ensures at the events query time (such as
UsageStatsService query), all the conversations are already available in
People Service to avoid missing some events (events without an
associated conversation are dropped).

Change-Id: I5248afa2771588a2434c335867de4e2d1a2eb6f3
Test: atest com.android.server.people.data.DataManagerTest
Test: atest com.android.server.people.data.ConversationStoreTest
Test: atest com.android.server.people.data.PackageDataTest
Bug: 150733302
parent 18fecf78
Loading
Loading
Loading
Loading
+15 −19
Original line number Diff line number Diff line
@@ -89,10 +89,8 @@ class ConversationStore {
     * Loads conversations from disk to memory in a background thread. This should be called
     * after the device powers on and the user has been unlocked.
     */
    @MainThread
    void loadConversationsFromDisk() {
        mScheduledExecutorService.execute(() -> {
            synchronized (this) {
    @WorkerThread
    synchronized void loadConversationsFromDisk() {
        ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
                getConversationInfosProtoDiskReadWriter();
        if (conversationInfosProtoDiskReadWriter == null) {
@@ -107,8 +105,6 @@ class ConversationStore {
            updateConversationsInMemory(conversationInfo);
        }
    }
        });
    }

    /**
     * Immediately flushes current conversations to disk. This should be called when device is
+111 −93
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -87,13 +88,13 @@ public class DataManager {

    private static final String TAG = "DataManager";

    private static final long QUERY_EVENTS_MAX_AGE_MS = DateUtils.DAY_IN_MILLIS;
    private static final long QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS;
    private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;

    private final Context mContext;
    private final Injector mInjector;
    private final ScheduledExecutorService mUsageStatsQueryExecutor;
    private final ScheduledExecutorService mDiskReadWriterExecutor;
    private final ScheduledExecutorService mScheduledExecutor;
    private final Object mLock = new Object();

    private final SparseArray<UserData> mUserDataArray = new SparseArray<>();
    private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>();
@@ -118,8 +119,7 @@ public class DataManager {
    DataManager(Context context, Injector injector) {
        mContext = context;
        mInjector = injector;
        mUsageStatsQueryExecutor = mInjector.createScheduledExecutor();
        mDiskReadWriterExecutor = mInjector.createScheduledExecutor();
        mScheduledExecutor = mInjector.createScheduledExecutor();
    }

    /** Initialization. Called when the system services are up running. */
@@ -138,68 +138,21 @@ public class DataManager {

    /** This method is called when a user is unlocked. */
    public void onUserUnlocked(int userId) {
        synchronized (mLock) {
            UserData userData = mUserDataArray.get(userId);
            if (userData == null) {
            userData = new UserData(userId, mDiskReadWriterExecutor);
                userData = new UserData(userId, mScheduledExecutor);
                mUserDataArray.put(userId, userData);
            }
            userData.setUserUnlocked();
        updateDefaultDialer(userData);
        updateDefaultSmsApp(userData);

        ScheduledFuture<?> scheduledFuture = mUsageStatsQueryExecutor.scheduleAtFixedRate(
                new UsageStatsQueryRunnable(userId), 1L, USAGE_STATS_QUERY_INTERVAL_SEC,
                TimeUnit.SECONDS);
        mUsageStatsQueryFutures.put(userId, scheduledFuture);

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
        intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
        BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
        mBroadcastReceivers.put(userId, broadcastReceiver);
        mContext.registerReceiverAsUser(
                broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);

        ContentObserver contactsContentObserver = new ContactsContentObserver(
                BackgroundThread.getHandler());
        mContactsContentObservers.put(userId, contactsContentObserver);
        mContext.getContentResolver().registerContentObserver(
                Contacts.CONTENT_URI, /* notifyForDescendants= */ true,
                contactsContentObserver, userId);

        NotificationListener notificationListener = new NotificationListener();
        mNotificationListeners.put(userId, notificationListener);
        try {
            notificationListener.registerAsSystemService(mContext,
                    new ComponentName(mContext, getClass()), userId);
        } catch (RemoteException e) {
            // Should never occur for local calls.
        }

        PackageMonitor packageMonitor = new PerUserPackageMonitor();
        packageMonitor.register(mContext, null, UserHandle.of(userId), true);
        mPackageMonitors.put(userId, packageMonitor);

        if (userId == UserHandle.USER_SYSTEM) {
            // The call log and MMS/SMS messages are shared across user profiles. So only need to
            // register the content observers once for the primary user.
            // TODO: Register observers after the conversations and events being loaded from disk.
            mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler());
            mContext.getContentResolver().registerContentObserver(
                    CallLog.CONTENT_URI, /* notifyForDescendants= */ true,
                    mCallLogContentObserver, UserHandle.USER_SYSTEM);

            mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler());
            mContext.getContentResolver().registerContentObserver(
                    MmsSms.CONTENT_URI, /* notifyForDescendants= */ false,
                    mMmsSmsContentObserver, UserHandle.USER_SYSTEM);
        }

        DataMaintenanceService.scheduleJob(mContext, userId);
        mScheduledExecutor.execute(() -> setupUser(userId));
    }

    /** This method is called when a user is stopping. */
    public void onUserStopping(int userId) {
        synchronized (mLock) {
            ContentResolver contentResolver = mContext.getContentResolver();
            if (mUserDataArray.indexOfKey(userId) >= 0) {
                mUserDataArray.get(userId).setUserStopped();
            }
@@ -210,8 +163,7 @@ public class DataManager {
                mContext.unregisterReceiver(mBroadcastReceivers.get(userId));
            }
            if (mContactsContentObservers.indexOfKey(userId) >= 0) {
            mContext.getContentResolver().unregisterContentObserver(
                    mContactsContentObservers.get(userId));
                contentResolver.unregisterContentObserver(mContactsContentObservers.get(userId));
            }
            if (mNotificationListeners.indexOfKey(userId) >= 0) {
                try {
@@ -225,17 +177,18 @@ public class DataManager {
            }
            if (userId == UserHandle.USER_SYSTEM) {
                if (mCallLogContentObserver != null) {
                mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver);
                    contentResolver.unregisterContentObserver(mCallLogContentObserver);
                    mCallLogContentObserver = null;
                }
                if (mMmsSmsContentObserver != null) {
                mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver);
                    contentResolver.unregisterContentObserver(mMmsSmsContentObserver);
                    mCallLogContentObserver = null;
                }
            }

            DataMaintenanceService.cancelJob(mContext, userId);
        }
    }

    /**
     * Iterates through all the {@link PackageData}s owned by the unlocked users who are in the
@@ -288,6 +241,9 @@ public class DataManager {
            return;
        }
        UserData userData = getUnlockedUserData(appTarget.getUser().getIdentifier());
        if (userData == null) {
            return;
        }
        PackageData packageData = userData.getOrCreatePackageData(appTarget.getPackageName());
        String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
        @Event.EventType int eventType = mimeTypeToShareEventType(mimeType);
@@ -353,6 +309,68 @@ public class DataManager {
        userData.restore(payload);
    }

    private void setupUser(@UserIdInt int userId) {
        synchronized (mLock) {
            UserData userData = getUnlockedUserData(userId);
            if (userData == null) {
                return;
            }
            userData.loadUserData();

            updateDefaultDialer(userData);
            updateDefaultSmsApp(userData);

            ScheduledFuture<?> scheduledFuture = mScheduledExecutor.scheduleAtFixedRate(
                    new UsageStatsQueryRunnable(userId), 1L, USAGE_STATS_QUERY_INTERVAL_SEC,
                    TimeUnit.SECONDS);
            mUsageStatsQueryFutures.put(userId, scheduledFuture);

            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
            intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
            BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
            mBroadcastReceivers.put(userId, broadcastReceiver);
            mContext.registerReceiverAsUser(
                    broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);

            ContentObserver contactsContentObserver = new ContactsContentObserver(
                    BackgroundThread.getHandler());
            mContactsContentObservers.put(userId, contactsContentObserver);
            mContext.getContentResolver().registerContentObserver(
                    Contacts.CONTENT_URI, /* notifyForDescendants= */ true,
                    contactsContentObserver, userId);

            NotificationListener notificationListener = new NotificationListener();
            mNotificationListeners.put(userId, notificationListener);
            try {
                notificationListener.registerAsSystemService(mContext,
                        new ComponentName(mContext, getClass()), userId);
            } catch (RemoteException e) {
                // Should never occur for local calls.
            }

            PackageMonitor packageMonitor = new PerUserPackageMonitor();
            packageMonitor.register(mContext, null, UserHandle.of(userId), true);
            mPackageMonitors.put(userId, packageMonitor);

            if (userId == UserHandle.USER_SYSTEM) {
                // The call log and MMS/SMS messages are shared across user profiles. So only need
                // to register the content observers once for the primary user.
                mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler());
                mContext.getContentResolver().registerContentObserver(
                        CallLog.CONTENT_URI, /* notifyForDescendants= */ true,
                        mCallLogContentObserver, UserHandle.USER_SYSTEM);

                mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler());
                mContext.getContentResolver().registerContentObserver(
                        MmsSms.CONTENT_URI, /* notifyForDescendants= */ false,
                        mMmsSmsContentObserver, UserHandle.USER_SYSTEM);
            }

            DataMaintenanceService.scheduleJob(mContext, userId);
        }
    }

    private int mimeTypeToShareEventType(String mimeType) {
        if (mimeType.startsWith("text/")) {
            return Event.TYPE_SHARE_TEXT;
+11 −15
Original line number Diff line number Diff line
@@ -17,9 +17,9 @@
package com.android.server.people.data;

import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.net.Uri;
import android.util.ArrayMap;

@@ -90,10 +90,8 @@ class EventStore {
     * Loads existing {@link EventHistoryImpl}s from disk. This should be called when device powers
     * on and user is unlocked.
     */
    @MainThread
    void loadFromDisk() {
        mScheduledExecutorService.execute(() -> {
            synchronized (this) {
    @WorkerThread
    synchronized void loadFromDisk() {
        for (@EventCategory int category = 0; category < mEventsCategoryDirs.size();
                category++) {
            File categoryDir = mEventsCategoryDirs.get(category);
@@ -103,8 +101,6 @@ class EventStore {
            mEventHistoryMaps.get(category).putAll(existingEventHistoriesImpl);
        }
    }
        });
    }

    /**
     * Flushes all {@link EventHistoryImpl}s to disk. Should be called when device is shutting down.
+2 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static com.android.server.people.data.EventStore.CATEGORY_SMS;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.content.LocusId;
import android.os.FileUtils;
import android.text.TextUtils;
@@ -77,6 +78,7 @@ public class PackageData {
     * Returns a map of package directory names as keys and their associated {@link PackageData}.
     * This should be called when device is powered on and unlocked.
     */
    @WorkerThread
    @NonNull
    static Map<String, PackageData> packagesDataFromDisk(@UserIdInt int userId,
            @NonNull Predicate<String> isDefaultDialerPredicate,
+10 −6
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.people.data;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.os.Environment;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -75,12 +76,6 @@ class UserData {

    void setUserUnlocked() {
        mIsUnlocked = true;

        // Ensures per user root directory for people data is present, and attempt to load
        // data from disk.
        mPerUserPeopleDataDir.mkdirs();
        mPackageDataMap.putAll(PackageData.packagesDataFromDisk(mUserId, this::isDefaultDialer,
                this::isDefaultSmsApp, mScheduledExecutorService, mPerUserPeopleDataDir));
    }

    void setUserStopped() {
@@ -91,6 +86,15 @@ class UserData {
        return mIsUnlocked;
    }

    @WorkerThread
    void loadUserData() {
        mPerUserPeopleDataDir.mkdir();
        Map<String, PackageData> packageDataMap = PackageData.packagesDataFromDisk(
                mUserId, this::isDefaultDialer, this::isDefaultSmsApp, mScheduledExecutorService,
                mPerUserPeopleDataDir);
        mPackageDataMap.putAll(packageDataMap);
    }

    /**
     * Gets the {@link PackageData} for the specified {@code packageName} if exists; otherwise
     * creates a new instance and returns it.
Loading