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

Commit c80e60dc authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Making UserCache the source of truth for all user events

Bug: 243688989
Test: Presubmit
Flag: N/A
Change-Id: I0e6b853d965eff1abaeb3b26dd6b94424e5212df
parent a8aefb6e
Loading
Loading
Loading
Loading
+5 −8
Original line number Diff line number Diff line
@@ -183,7 +183,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
@@ -208,7 +207,6 @@ import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
import com.android.launcher3.util.SystemUiController;
@@ -411,8 +409,6 @@ public class Launcher extends StatefulActivity<LauncherState>
    protected long mLastTouchUpTime = -1;
    private boolean mTouchInProgress;

    private SafeCloseable mUserChangedCallbackCloseable;

    // New InstanceId is assigned to mAllAppsSessionLogId for each AllApps sessions.
    // When Launcher is not in AllApps state mAllAppsSessionLogId will be null.
    // User actions within AllApps state are logged with this InstanceId, to recreate AllApps
@@ -581,9 +577,6 @@ public class Launcher extends StatefulActivity<LauncherState>
        mRotationHelper.initialize();
        TraceHelper.INSTANCE.endSection();

        mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
                () -> getStateManager().goToState(NORMAL));

        if (Utilities.ATLEAST_R) {
            getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
        }
@@ -1804,7 +1797,6 @@ public class Launcher extends StatefulActivity<LauncherState>
        LauncherAppState.getIDP(this).removeOnChangeListener(this);

        mOverlayManager.onActivityDestroyed(this);
        mUserChangedCallbackCloseable.close();
    }

    public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -2974,9 +2966,14 @@ public class Launcher extends StatefulActivity<LauncherState>
    public void bindAllApplications(AppInfo[] apps, int flags,
            Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
        Preconditions.assertUIThread();
        boolean hadWorkApps = mAppsView.shouldShowTabs();
        AllAppsStore appsStore = mAppsView.getAppsStore();
        appsStore.setApps(apps, flags, packageUserKeytoUidMap);
        PopupContainerWithArrow.dismissInvalidPopup(this);
        if (hadWorkApps != mAppsView.shouldShowTabs()) {
            getStateManager().goToState(NORMAL);
        }

        if (Utilities.ATLEAST_S) {
            Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
                    DISPLAY_ALL_APPS_TRACE_COOKIE);
+1 −5
Original line number Diff line number Diff line
@@ -111,10 +111,6 @@ public class LauncherAppState implements SafeCloseable {
        SimpleBroadcastReceiver modelChangeReceiver =
                new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
        modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
                Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
                Intent.ACTION_PROFILE_INACCESSIBLE,
                ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
        if (FeatureFlags.IS_STUDIO_BUILD) {
            modelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
@@ -122,7 +118,7 @@ public class LauncherAppState implements SafeCloseable {
        mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));

        SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
                .addUserChangeListener(mModel::forceReload);
                .addUserEventListener(mModel::onUserEvent);
        mOnTerminateCallback.add(userChangeListener::close);

        LockedUserState.get(context).runOnUserUnlocked(() -> {
+27 −31
Original line number Diff line number Diff line
@@ -95,15 +95,6 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi

    static final String TAG = "Launcher.Model";

    // Broadcast intent to track when the profile gets locked:
    // ACTION_MANAGED_PROFILE_UNAVAILABLE can be used until Android U where profile no longer gets
    // locked when paused.
    // ACTION_PROFILE_INACCESSIBLE always means that the profile is getting locked but it only
    // appeared in Android S.
    private static final String ACTION_PROFILE_LOCKED = Utilities.ATLEAST_U
            ? Intent.ACTION_PROFILE_INACCESSIBLE
            : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;

    @NonNull
    private final LauncherAppState mApp;
    @NonNull
@@ -303,28 +294,6 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
            // If we have changed locale we need to clear out the labels in all apps/workspace.
            forceReload();
        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
                || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)
                || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)
                || Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)) {
            UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
            if (TestProtocol.sDebugTracing) {
                Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: " + action +
                        " user: " + user);
            }
            if (user != null) {
                if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
                        Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
                    enqueueModelUpdateTask(new PackageUpdatedTask(
                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
                }

                if (ACTION_PROFILE_LOCKED.equals(action)
                        || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
                    enqueueModelUpdateTask(new UserLockStateChangedTask(
                            user, Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)));
                }
            }
        } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
            enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
        } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
@@ -336,6 +305,33 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
        }
    }

    /**
     * Called then there use a user event
     * @see UserCache#addUserEventListener
     */
    public void onUserEvent(UserHandle user, String action) {
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: "
                    + action + " user: " + user);
        }

        if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
                || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
            enqueueModelUpdateTask(new PackageUpdatedTask(
                    PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
        }

        if (UserCache.ACTION_PROFILE_LOCKED.equals(action)
                || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) {
            enqueueModelUpdateTask(new UserLockStateChangedTask(
                    user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action)));
        }
        if (UserCache.ACTION_PROFILE_ADDED.equals(action)
                || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
            forceReload();
        }
    }

    /**
     * Reloads the workspace items from the DB and re-binds the workspace. This should generally
     * not be called as DB updates are automatically followed by UI update
+4 −1
Original line number Diff line number Diff line
@@ -1100,7 +1100,10 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
        }
    }

    protected boolean shouldShowTabs() {
    /**
     * Returns true if the container has work apps.
     */
    public boolean shouldShowTabs() {
        return mHasWorkApps;
    }

+80 −88
Original line number Diff line number Diff line
@@ -16,16 +16,21 @@

package com.android.launcher3.pm;

import static com.android.launcher3.Utilities.ATLEAST_U;
import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;

import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.LongSparseArray;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;

import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
@@ -34,136 +39,123 @@ import com.android.launcher3.util.SimpleBroadcastReceiver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

/**
 * Class which manages a local cache of user handles to avoid system rpc
 */
public class UserCache {
public class UserCache implements SafeCloseable {

    public static final String ACTION_PROFILE_ADDED = ATLEAST_U
            ? Intent.ACTION_PROFILE_ADDED : Intent.ACTION_MANAGED_PROFILE_ADDED;
    public static final String ACTION_PROFILE_REMOVED = ATLEAST_U
            ? Intent.ACTION_PROFILE_REMOVED : Intent.ACTION_MANAGED_PROFILE_REMOVED;

    public static final String ACTION_PROFILE_UNLOCKED = ATLEAST_U
            ? Intent.ACTION_PROFILE_ACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNLOCKED;
    public static final String ACTION_PROFILE_LOCKED = ATLEAST_U
            ? Intent.ACTION_PROFILE_INACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;

    public static final MainThreadInitializedObject<UserCache> INSTANCE =
            new MainThreadInitializedObject<>(UserCache::new);

    private final Context mContext;
    private final UserManager mUserManager;
    private final ArrayList<Runnable> mUserChangeListeners = new ArrayList<>();
    private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
    private final SimpleBroadcastReceiver mUserChangeReceiver =
            new SimpleBroadcastReceiver(this::onUsersChanged);

    private LongSparseArray<UserHandle> mUsers;
    // Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
    // and not {@link Object#equals}
    private ArrayMap<UserHandle, Long> mUserToSerialMap;
    private final Context mContext;

    @NonNull
    private Map<UserHandle, Long> mUserToSerialMap;

    private UserCache(Context context) {
        mContext = context;
        mUserManager = context.getSystemService(UserManager.class);
        mUserToSerialMap = Collections.emptyMap();
        MODEL_EXECUTOR.execute(this::initAsync);
    }

    private void onUsersChanged(Intent intent) {
        testLogD(WORK_TAB_MISSING, "onUsersChanged intent: " + intent);
        enableAndResetCache();
        mUserChangeListeners.forEach(Runnable::run);
    @Override
    public void close() {
        MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
    }

    /**
     * Adds a listener for user additions and removals
     */
    public SafeCloseable addUserChangeListener(Runnable command) {
        synchronized (this) {
            if (mUserChangeListeners.isEmpty()) {
                // Enable caching and start listening for user broadcast
    @WorkerThread
    private void initAsync() {
        mUserChangeReceiver.register(mContext,
                        Intent.ACTION_MANAGED_PROFILE_ADDED,
                        Intent.ACTION_MANAGED_PROFILE_REMOVED);
                enableAndResetCache();
            }
            mUserChangeListeners.add(command);
            return () -> removeUserChangeListener(command);
        }
                Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                ACTION_PROFILE_ADDED,
                ACTION_PROFILE_REMOVED,
                ACTION_PROFILE_UNLOCKED,
                ACTION_PROFILE_LOCKED);
        updateCache();
    }

    private void enableAndResetCache() {
        synchronized (this) {
            mUsers = new LongSparseArray<>();
            mUserToSerialMap = new ArrayMap<>();
            List<UserHandle> users = mUserManager.getUserProfiles();
            if (users != null) {
                for (UserHandle user : users) {
                    testLogD(WORK_TAB_MISSING, "caching user: " + user);
                    long serial = mUserManager.getSerialNumberForUser(user);
                    mUsers.put(serial, user);
                    mUserToSerialMap.put(user, serial);
                }
            }
    @AnyThread
    private void onUsersChanged(Intent intent) {
        testLogD(WORK_TAB_MISSING, "onUsersChanged intent: " + intent);

        MODEL_EXECUTOR.execute(this::updateCache);
        UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
        if (user == null) {
            return;
        }
        String action = intent.getAction();
        mUserEventListeners.forEach(l -> l.accept(user, action));
    }

    private void removeUserChangeListener(Runnable command) {
        synchronized (this) {
            mUserChangeListeners.remove(command);
            if (mUserChangeListeners.isEmpty()) {
                // Disable cache and stop listening
                mContext.unregisterReceiver(mUserChangeReceiver);

                mUsers = null;
                mUserToSerialMap = null;
            }
    @WorkerThread
    private void updateCache() {
        mUserToSerialMap = queryAllUsers(mContext.getSystemService(UserManager.class));
    }

    /**
     * Adds a listener for user additions and removals
     */
    public SafeCloseable addUserEventListener(BiConsumer<UserHandle, String> listener) {
        mUserEventListeners.add(listener);
        return () -> mUserEventListeners.remove(listener);
    }

    /**
     * @see UserManager#getSerialNumberForUser(UserHandle)
     */
    public long getSerialNumberForUser(UserHandle user) {
        synchronized (this) {
            if (mUserToSerialMap != null) {
        Long serial = mUserToSerialMap.get(user);
        return serial == null ? 0 : serial;
    }
        }
        return mUserManager.getSerialNumberForUser(user);
    }

    /**
     * @see UserManager#getUserForSerialNumber(long)
     */
    public UserHandle getUserForSerialNumber(long serialNumber) {
        synchronized (this) {
            if (mUsers != null) {
                return mUsers.get(serialNumber);
            }
        }
        return mUserManager.getUserForSerialNumber(serialNumber);
        Long value = serialNumber;
        return mUserToSerialMap
                .entrySet()
                .stream()
                .filter(entry -> value.equals(entry.getValue()))
                .findFirst()
                .map(Map.Entry::getKey)
                .orElse(Process.myUserHandle());
    }

    /**
     * @see UserManager#getUserProfiles()
     */
    public List<UserHandle> getUserProfiles() {
        StringBuilder usersToReturn = new StringBuilder();
        List<UserHandle> users;
        if (sDebugTracing) {
            users = mUserManager.getUserProfiles();
            for (UserHandle u : users) {
                usersToReturn.append(u).append(" && ");
            }
            testLogD(WORK_TAB_MISSING, "users from userManager: " + usersToReturn);
        return List.copyOf(mUserToSerialMap.keySet());
    }

        synchronized (this) {
            if (mUsers != null) {
                usersToReturn = new StringBuilder();
                for (UserHandle u : mUserToSerialMap.keySet()) {
                    usersToReturn.append(u).append(" && ");
                }
                testLogD(WORK_TAB_MISSING, "users from cache: " + usersToReturn);
                return new ArrayList<>(mUserToSerialMap.keySet());
            } else {
                testLogD(WORK_TAB_MISSING, "users from cache null");
    private static Map<UserHandle, Long> queryAllUsers(UserManager userManager) {
        Map<UserHandle, Long> users = new ArrayMap<>();
        List<UserHandle> usersActual = userManager.getUserProfiles();
        if (usersActual != null) {
            for (UserHandle user : usersActual) {
                long serial = userManager.getSerialNumberForUser(user);
                users.put(user, serial);
            }
        }

        users = mUserManager.getUserProfiles();
        return users == null ? Collections.emptyList() : users;
        return users;
    }
}