From ce495f13e3fbb449674e783d9cc05467d18e16fc Mon Sep 17 00:00:00 2001 From: Himanshu Gupta Date: Mon, 11 Dec 2023 16:19:55 +0000 Subject: [PATCH] Adding suport for Private Space QsTile fulfillment. Adding logic to unlock private space and scroll to the container after unlock event is received by Launcher. This change also moves pieces of Private Space Animation to different classes, in order to make it more robust. Bug: 289024009 Test: atest PrivateProfileManagerTest Flag: ACONFIG com.google.android.apps.nexuslauncher.inject_private_space_tile TEAMFOOD Change-Id: Ica2fbc00ff3516ed5aca7fbbfc0bd2aa036a4cee --- .../allapps/ActivityAllAppsContainerView.java | 7 +++- .../allapps/AlphabeticalAppsList.java | 4 -- .../allapps/PrivateProfileManager.java | 39 +++++++++++++------ .../PrivateSpaceHeaderViewController.java | 26 ++++++++++++- .../launcher3/allapps/UserProfileManager.java | 2 - .../allapps/PrivateProfileManagerTest.java | 26 ++++++++++++- .../PrivateSpaceHeaderViewControllerTest.java | 5 ++- 7 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index ad764e3867..55438fe028 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -260,7 +260,7 @@ public class ActivityAllAppsContainerView mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider(); if (Flags.enablePrivateSpace()) { mPrivateSpaceHeaderViewController = - new PrivateSpaceHeaderViewController(mPrivateProfileManager); + new PrivateSpaceHeaderViewController(this, mPrivateProfileManager); } mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN, @@ -980,6 +980,11 @@ public class ActivityAllAppsContainerView return mWorkManager; } + /** Returns whether Private Profile has been setup. */ + public boolean hasPrivateProfile() { + return mHasPrivateApps; + } + @Override public void onDeviceProfileChanged(DeviceProfile dp) { for (AdapterHolder holder : mAH) { diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 35c07c3e77..ad875e0dc3 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -325,10 +325,6 @@ public class AlphabeticalAppsList implement mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems); position++; addAppsWithSections(mPrivateApps, position); - if (mActivityContext.getAppsView() != null) { - mActivityContext.getAppsView().getActiveRecyclerView() - .scrollToBottomWithMotion(); - } break; } } diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index c99b69fce2..aee511cad7 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -21,6 +21,7 @@ import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER; import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI; @@ -34,7 +35,6 @@ import android.os.UserManager; import androidx.annotation.VisibleForTesting; import com.android.launcher3.BuildConfig; -import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; @@ -59,11 +59,11 @@ public class PrivateProfileManager extends UserProfileManager { private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER; private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key"; private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal"; - private static final int ANIMATION_DURATION = 2000; private final ActivityAllAppsContainerView mAllApps; private final Predicate mPrivateProfileMatcher; private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator; private boolean mPrivateSpaceSettingsAvailable; + private Runnable mUnlockRunnable; public PrivateProfileManager(UserManager userManager, ActivityAllAppsContainerView allApps, @@ -115,9 +115,17 @@ public class PrivateProfileManager extends UserProfileManager { mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1); } - /** Disables quiet mode for Private Space User Profile. */ - public void unlockPrivateProfile() { + /** + * Disables quiet mode for Private Space User Profile. + * The runnable passed will be executed in the {@link #reset()} method, + * when Launcher receives update about profile availability. + * The runnable passed is only executed once, and reset after execution. + * In case the method is called again, before the previously set runnable was executed, + * the runnable will be updated. + */ + public void unlockPrivateProfile(Runnable runnable) { enableQuietMode(false); + mUnlockRunnable = runnable; } /** Enables quiet mode for Private Space User Profile. */ @@ -133,11 +141,15 @@ public class PrivateProfileManager extends UserProfileManager { /** Resets the current state of Private Profile, w.r.t. to Launcher. */ public void reset() { + int previousState = getCurrentState(); boolean isEnabled = !mAllApps.getAppsStore() .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED); int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED; setCurrentState(updatedState); resetPrivateSpaceDecorator(updatedState); + if (transitioningFromLockedToUnlocked(previousState, updatedState)) { + applyUnlockRunnable(); + } } /** Opens the Private Space Settings Entry Point. */ @@ -182,13 +194,6 @@ public class PrivateProfileManager extends UserProfileManager { } // Add Private Space Decorator to the Recycler view. mainAdapterHolder.mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator); - if (Flags.privateSpaceAnimation() && mAllApps.getActiveRecyclerView() - == mainAdapterHolder.mRecyclerView) { - RecyclerViewAnimationController recyclerViewAnimationController = - new RecyclerViewAnimationController(mAllApps); - recyclerViewAnimationController.animateToState(true /* expand */, - ANIMATION_DURATION, () -> {}); - } } else { // Remove Private Space Decorator from the Recycler view. if (mPrivateAppsSectionDecorator != null) { @@ -202,6 +207,18 @@ public class PrivateProfileManager extends UserProfileManager { setQuietMode(enable); } + void applyUnlockRunnable() { + if (mUnlockRunnable != null) { + // reset the runnable to prevent re-execution. + MAIN_EXECUTOR.post(mUnlockRunnable); + mUnlockRunnable = null; + } + } + + private boolean transitioningFromLockedToUnlocked(int previousState, int updatedState) { + return previousState == STATE_DISABLED && updatedState == STATE_ENABLED; + } + @Override public Predicate getUserMatcher() { return mPrivateProfileMatcher; diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java index 568ce32fb9..bc3269df2a 100644 --- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java +++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java @@ -16,6 +16,7 @@ package com.android.launcher3.allapps; +import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN; import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED; import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED; import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION; @@ -28,6 +29,7 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; +import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.allapps.UserProfileManager.UserProfileState; @@ -36,9 +38,13 @@ import com.android.launcher3.allapps.UserProfileManager.UserProfileState; * {@link UserProfileState} */ public class PrivateSpaceHeaderViewController { + private static final int ANIMATION_DURATION = 2000; + private final ActivityAllAppsContainerView mAllApps; private final PrivateProfileManager mPrivateProfileManager; - public PrivateSpaceHeaderViewController(PrivateProfileManager privateProfileManager) { + public PrivateSpaceHeaderViewController(ActivityAllAppsContainerView allApps, + PrivateProfileManager privateProfileManager) { + this.mAllApps = allApps; this.mPrivateProfileManager = privateProfileManager; } @@ -77,7 +83,8 @@ public class PrivateSpaceHeaderViewController { quietModeButton.setOnClickListener( view -> { mPrivateProfileManager.logEvents(LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP); - mPrivateProfileManager.unlockPrivateProfile(); + mPrivateProfileManager.unlockPrivateProfile((this:: + onPrivateProfileUnlocked)); }); } default -> quietModeButton.setVisibility(View.GONE); @@ -106,6 +113,21 @@ public class PrivateSpaceHeaderViewController { } } + private void onPrivateProfileUnlocked() { + // If we are on main adapter view, we apply the PS Container expansion animation and + // then scroll down to load the entire container, making animation visible. + ActivityAllAppsContainerView.AdapterHolder mainAdapterHolder = + (ActivityAllAppsContainerView.AdapterHolder) mAllApps.mAH.get(MAIN); + if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation() + && mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) { + RecyclerViewAnimationController recyclerViewAnimationController = + new RecyclerViewAnimationController(mAllApps); + recyclerViewAnimationController.animateToState(true /* expand */, + ANIMATION_DURATION, () -> {}); + mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(); + } + } + PrivateProfileManager getPrivateProfileManager() { return mPrivateProfileManager; } diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java index 6bef7256ff..8894f45692 100644 --- a/src/com/android/launcher3/allapps/UserProfileManager.java +++ b/src/com/android/launcher3/allapps/UserProfileManager.java @@ -22,7 +22,6 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogManager; @@ -89,7 +88,6 @@ public abstract class UserProfileManager { } /** Returns current state for the profile type. */ - @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public int getCurrentState() { return mCurrentState; } diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java index 79d00c9f2f..24f9acd2d2 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java @@ -22,6 +22,7 @@ import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PRO import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; @@ -81,6 +82,8 @@ public class PrivateProfileManagerTest { @Mock private PackageManager mPackageManager; + private boolean mRunnableCalled = false; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -110,7 +113,7 @@ public class PrivateProfileManagerTest { public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception { when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true); - mPrivateProfileManager.unlockPrivateProfile(); + mPrivateProfileManager.unlockPrivateProfile(() -> {}); awaitTasksCompleted(); Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE); @@ -132,6 +135,23 @@ public class PrivateProfileManagerTest { assertEquals(STATE_DISABLED, privateProfileManager.getCurrentState()); } + @Test + public void transitioningToUnlocked_resetCallsPendingRunnable() throws Exception { + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + doNothing().when(privateProfileManager).resetPrivateSpaceDecorator(anyInt()); + when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)) + .thenReturn(false); + when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); + mRunnableCalled = false; + + privateProfileManager.unlockPrivateProfile(this::testRunnable); + privateProfileManager.reset(); + + awaitTasksCompleted(); + Mockito.verify(privateProfileManager).applyUnlockRunnable(); + assertTrue(mRunnableCalled); + } + @Test public void openPrivateSpaceSettings_triggersSecurityAndPrivacyIntent() { Intent expectedIntent = new Intent(SAFETY_CENTER_INTENT); @@ -150,4 +170,8 @@ public class PrivateProfileManagerTest { private static void awaitTasksCompleted() throws Exception { UI_HELPER_EXECUTOR.submit(() -> null).get(); } + + private void testRunnable() { + mRunnableCalled = true; + } } diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java index bc09cddba9..92fff49581 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java @@ -64,13 +64,16 @@ public class PrivateSpaceHeaderViewControllerTest { private RelativeLayout mPsHeaderLayout; @Mock private PrivateProfileManager mPrivateProfileManager; + @Mock + private ActivityAllAppsContainerView mAllApps; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = new ActivityContextWrapper(getApplicationContext()); mLayoutInflater = LayoutInflater.from(getApplicationContext()); - mPsHeaderViewController = new PrivateSpaceHeaderViewController(mPrivateProfileManager); + mPsHeaderViewController = new PrivateSpaceHeaderViewController(mAllApps, + mPrivateProfileManager); mPsHeaderLayout = (RelativeLayout) mLayoutInflater.inflate(R.layout.private_space_header, null); }