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