diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index e4e2eb2769..6f9cbfd74a 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -27,6 +27,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; +import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition; @@ -186,6 +188,10 @@ public class AppPairsController { } @PersistentSnapPosition int snapPosition = gtv.getSnapPosition(); + if (snapPosition == SNAP_TO_NONE) { + // Free snap mode is enabled, just save it as 50/50 split. + snapPosition = SNAP_TO_50_50; + } if (!isPersistentSnapPosition(snapPosition)) { // If we received an illegal snap position, log an error and do not create the app pair Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition " diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java index d729bdc2e2..604d072ad5 100644 --- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -49,6 +50,7 @@ import java.util.Arrays; */ public class OverviewActionsView extends FrameLayout implements OnClickListener, Insettable { + public static final String TAG = "OverviewActionsView"; private final Rect mInsets = new Rect(); @IntDef(flag = true, value = { @@ -254,6 +256,8 @@ public class OverviewActionsView extends FrameLayo * pair. */ public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) { + Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask + + "], canSaveAppPair = [" + canSaveAppPair + "]"); mIsGroupedTask = isGroupedTask; mCanSaveAppPair = canSaveAppPair; updateActionButtonsVisibility(); @@ -273,6 +277,8 @@ public class OverviewActionsView extends FrameLayo assert mDp != null; boolean showSingleTaskActions = !mIsGroupedTask; boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair; + Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = [" + + showSingleTaskActions + ", showGroupActions = [" + showGroupActions + "]"); getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0); getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0); } diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java index a67a3624f8..98ca420eb9 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java @@ -300,7 +300,7 @@ public abstract class BaseAllAppsAdapter ex case VIEW_TYPE_PRIVATE_SPACE_HEADER: RelativeLayout psHeaderLayout = holder.itemView.findViewById( R.id.ps_header_layout); - mApps.getPrivateProfileManager().addPrivateSpaceHeaderViewElements(psHeaderLayout); + mApps.getPrivateProfileManager().bindPrivateSpaceHeaderViewElements(psHeaderLayout); AdapterItem adapterItem = mApps.getAdapterItems().get(position); int roundRegions = ROUND_TOP_LEFT | ROUND_TOP_RIGHT; if (mApps.getPrivateProfileManager().getCurrentState() == STATE_DISABLED) { diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index a620490e4e..2765c5cdb0 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -104,6 +104,7 @@ public class PrivateProfileManager extends UserProfileManager { private static final int LOCK_TEXT_OPACITY_DELAY = 500; private static final int MASK_VIEW_DELAY = 400; private static final int NO_DELAY = 0; + private static final int CONTAINER_OPACITY_DURATION = 150; private final ActivityAllAppsContainerView mAllApps; private final Predicate mPrivateProfileMatcher; private final int mPsHeaderHeight; @@ -196,23 +197,6 @@ public class PrivateProfileManager extends UserProfileManager { mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1); } - /** - * Disables quiet mode for Private Space User Profile. - * When called from search, a runnable is set and executed in the {@link #reset()} method, when - * Launcher receives update about profile availability. - * The runnable 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() { - setQuietMode(false); - } - - /** Enables quiet mode for Private Space User Profile. */ - void lockPrivateProfile() { - setQuietMode(true); - } - /** Whether private profile should be hidden on Launcher. */ public boolean isPrivateSpaceHidden() { return getCurrentState() == STATE_DISABLED && SettingsCache.INSTANCE @@ -377,7 +361,7 @@ public class PrivateProfileManager extends UserProfileManager { } /** Add Private Space Header view elements based upon {@link UserProfileState} */ - public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) { + public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) { mPSHeader = parent; if (mOnPSHeaderAdded != null) { MAIN_EXECUTOR.execute(mOnPSHeaderAdded); @@ -395,27 +379,27 @@ public class PrivateProfileManager extends UserProfileManager { //Add quietMode image and action for lock/unlock button ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button); assert lockButton != null; - addLockButton(lockButton); + updateLockButton(lockButton); //Trigger lock/unlock action from header. - addHeaderOnClickListener(mPSHeader); + updateHeaderOnClickListener(mPSHeader); //Add image and action for private space settings button ImageButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button); assert settingsButton != null; - addPrivateSpaceSettingsButton(settingsButton); + updatePrivateSpaceSettingsButton(settingsButton); //Add image for private space transitioning view ImageView transitionView = parent.findViewById(R.id.ps_transition_image); assert transitionView != null; - addTransitionImage(transitionView); + updateTransitionImage(transitionView); } /** * Adds the quietModeButton and attach onClickListener for the header to animate different * states when clicked. */ - private void addLockButton(ViewGroup lockButton) { + private void updateLockButton(ViewGroup lockButton) { TextView lockText = lockButton.findViewById(R.id.lock_text); switch (getCurrentState()) { case STATE_ENABLED -> { @@ -434,7 +418,7 @@ public class PrivateProfileManager extends UserProfileManager { } } - private void addHeaderOnClickListener(RelativeLayout header) { + private void updateHeaderOnClickListener(RelativeLayout header) { if (getCurrentState() == STATE_DISABLED) { header.setOnClickListener(view -> lockingAction(/* lock */ false)); header.setClickable(true); @@ -452,14 +436,10 @@ public class PrivateProfileManager extends UserProfileManager { /** Sets the enablement of the profile when header or button is clicked. */ private void lockingAction(boolean lock) { logEvents(lock ? LAUNCHER_PRIVATE_SPACE_LOCK_TAP : LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP); - if (lock) { - lockPrivateProfile(); - } else { - unlockPrivateProfile(); - } + setQuietMode(lock); } - private void addPrivateSpaceSettingsButton(ImageButton settingsButton) { + private void updatePrivateSpaceSettingsButton(ImageButton settingsButton) { if (getCurrentState() == STATE_ENABLED && isPrivateSpaceSettingsAvailable()) { settingsButton.setVisibility(VISIBLE); @@ -473,7 +453,7 @@ public class PrivateProfileManager extends UserProfileManager { } } - private void addTransitionImage(ImageView transitionImage) { + private void updateTransitionImage(ImageView transitionImage) { if (getCurrentState() == STATE_TRANSITION) { transitionImage.setVisibility(VISIBLE); } else { @@ -497,7 +477,10 @@ public class PrivateProfileManager extends UserProfileManager { return LinearSmoothScroller.SNAP_TO_END; } }; - smoothScroller.setTargetPosition(i); + // If privateSpaceHidden() then the entire container decorator will be invisible and + // we can directly move to an element above the header. There should always be one + // element, as PS is present in the bottom of All Apps. + smoothScroller.setTargetPosition(isPrivateSpaceHidden() ? i - 1 : i); RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager(); if (layoutManager != null) { startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller); @@ -619,8 +602,11 @@ public class PrivateProfileManager extends UserProfileManager { float newAlpha = (float) valueAnimator.getAnimatedValue(); for (int i = 0; i < allAppsAdapterItems.size(); i++) { BaseAllAppsAdapter.AdapterItem currentItem = allAppsAdapterItems.get(i); + // When not hidden: Fade all PS items except header. + // When hidden: Fade all items. if (isPrivateSpaceItem(currentItem) && - currentItem.viewType != VIEW_TYPE_PRIVATE_SPACE_HEADER) { + (currentItem.viewType != VIEW_TYPE_PRIVATE_SPACE_HEADER + || isPrivateSpaceHidden())) { RecyclerView.ViewHolder viewHolder = allAppsRecyclerView.findViewHolderForAdapterPosition(i); if (viewHolder != null) { @@ -702,10 +688,9 @@ public class PrivateProfileManager extends UserProfileManager { translateFloatingMaskView(false)); } else { if (isPrivateSpaceHidden()) { - animatorSet.playSequentially(translateFloatingMaskView(false), - animateAlphaOfIcons(false), - animateCollapseAnimation(), - fadeOutHeaderAlpha()); + animatorSet.playSequentially(animateAlphaOfIcons(false), + animateAlphaOfPrivateSpaceContainer(), + animateCollapseAnimation()); } else { animatorSet.playSequentially(translateFloatingMaskView(true), animateAlphaOfIcons(false), @@ -715,22 +700,23 @@ public class PrivateProfileManager extends UserProfileManager { animatorSet.start(); } - /** Fades out the private space container. */ - private ValueAnimator fadeOutHeaderAlpha() { - if (mPSHeader == null) { - return new ValueAnimator(); - } - float from = 1; - float to = 0; - ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to); - alphaAnim.setDuration(EXPAND_COLLAPSE_DURATION); - alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - if (mPSHeader != null) { - mPSHeader.setAlpha((float) valueAnimator.getAnimatedValue()); + /** Fades out the private space container (defined by its items' decorators). */ + private ValueAnimator animateAlphaOfPrivateSpaceContainer() { + int from = 255; // 100% opacity. + int to = 0; // No opacity. + ValueAnimator alphaAnim = ObjectAnimator.ofInt(from, to); + AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView(); + List allAppsAdapterItems = + allAppsRecyclerView.getApps().getAdapterItems(); + alphaAnim.setDuration(CONTAINER_OPACITY_DURATION); + alphaAnim.addUpdateListener(valueAnimator -> { + for (BaseAllAppsAdapter.AdapterItem currentItem : allAppsAdapterItems) { + if (isPrivateSpaceItem(currentItem)) { + currentItem.setDecorationFillAlpha((int) valueAnimator.getAnimatedValue()); } } + // Invalidate the parent view, to redraw the decorations with changed alpha. + allAppsRecyclerView.invalidate(); }); return alphaAnim; } diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt index ec8c9c2bf1..63833e4545 100644 --- a/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt @@ -32,7 +32,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.Mockito -import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever @SmallTest @@ -51,20 +51,21 @@ class WidgetDragScaleUtilsTest { deviceProfile = Mockito.spy(LauncherAppState.getIDP(context).getDeviceProfile(context).copy(context)) - doAnswer { - return@doAnswer 0.8f - } - .whenever(deviceProfile) - .getWorkspaceSpringLoadScale(any(Context::class.java)) - whenever(deviceProfile.cellSize).thenReturn(Point(CELL_SIZE, CELL_SIZE)) + doReturn(0.8f) + .whenever(deviceProfile).getWorkspaceSpringLoadScale(any(Context::class.java)) deviceProfile.cellLayoutBorderSpacePx = Point(CELL_SPACING, CELL_SPACING) deviceProfile.widgetPadding.setEmpty() } @Test fun getWidgetDragScalePx_largeDraggedView_downScaled() { + val minSize = context.resources.getDimensionPixelSize( + R.dimen.widget_drag_view_min_scale_down_size) + whenever(deviceProfile.cellSize).thenReturn(Point(minSize * 2, minSize * 2)) + itemInfo.spanX = 2 itemInfo.spanY = 2 + val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY) // Assume dragged view was a drawable which was larger than widget's size. val draggedViewWidthPx = widgetSize.width + 0.5f * widgetSize.width @@ -84,6 +85,9 @@ class WidgetDragScaleUtilsTest { @Test fun getWidgetDragScalePx_draggedViewSameAsWidgetSize_downScaled() { + val minSize = context.resources.getDimensionPixelSize( + R.dimen.widget_drag_view_min_scale_down_size) + whenever(deviceProfile.cellSize).thenReturn(Point(minSize * 2, minSize * 2)) itemInfo.spanX = 4 itemInfo.spanY = 2 @@ -109,14 +113,13 @@ class WidgetDragScaleUtilsTest { @Test fun getWidgetDragScalePx_draggedViewSmallerThanMinSize_scaledSizeIsAtLeastMinSize() { - itemInfo.spanX = 1 - itemInfo.spanY = 1 val minSizePx = context.resources.getDimensionPixelSize(R.dimen.widget_drag_view_min_scale_down_size) - // Assume min size is greater than cell size, so that, we know the upscale of dragged view // is due to min size enforcement. - assumeTrue(minSizePx > CELL_SIZE) + whenever(deviceProfile.cellSize).thenReturn(Point(minSizePx / 2, minSizePx / 2)) + itemInfo.spanX = 1 + itemInfo.spanY = 1 val draggedViewWidthPx = minSizePx - 15f val draggedViewHeightPx = minSizePx - 15f @@ -142,7 +145,6 @@ class WidgetDragScaleUtilsTest { } companion object { - const val CELL_SIZE = 60 const val CELL_SPACING = 10 } } diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java index 4cd2a0755a..5c50e9788b 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java @@ -130,7 +130,7 @@ public class PrivateProfileManagerTest { public void lockPrivateProfile_requestsQuietModeAsTrue() throws Exception { when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(false); - mPrivateProfileManager.lockPrivateProfile(); + mPrivateProfileManager.setQuietMode(true /* lock */); awaitTasksCompleted(); Mockito.verify(mUserManager).requestQuietModeEnabled(true, PRIVATE_HANDLE); @@ -140,7 +140,7 @@ public class PrivateProfileManagerTest { public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception { when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true); - mPrivateProfileManager.unlockPrivateProfile(); + mPrivateProfileManager.setQuietMode(false /* unlock */); awaitTasksCompleted(); Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE); @@ -176,7 +176,7 @@ public class PrivateProfileManagerTest { doNothing().when(privateProfileManager).expandPrivateSpace(); when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); - privateProfileManager.unlockPrivateProfile(); + privateProfileManager.setQuietMode(false /* unlock */); privateProfileManager.reset(); awaitTasksCompleted(); @@ -194,7 +194,7 @@ public class PrivateProfileManagerTest { doNothing().when(privateProfileManager).expandPrivateSpace(); when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); - privateProfileManager.lockPrivateProfile(); + privateProfileManager.setQuietMode(true /* lock */); privateProfileManager.reset(); awaitTasksCompleted(); diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java index 512b2acc10..eac7f63fb9 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java @@ -133,7 +133,7 @@ public class PrivateSpaceHeaderViewTest { Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.ic_lock)); PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); - privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); awaitTasksCompleted(); int totalContainerHeaderView = 0; @@ -168,7 +168,7 @@ public class PrivateSpaceHeaderViewTest { PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(true); - privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); awaitTasksCompleted(); int totalContainerHeaderView = 0; @@ -210,7 +210,7 @@ public class PrivateSpaceHeaderViewTest { PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(false); - privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); awaitTasksCompleted(); int totalContainerHeaderView = 0; @@ -248,7 +248,7 @@ public class PrivateSpaceHeaderViewTest { Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image)); PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); when(privateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION); - privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout); awaitTasksCompleted(); int totalContainerHeaderView = 0;