From 892b96f3191b1d635982207624364eeba29f91ae Mon Sep 17 00:00:00 2001 From: Brandon Dayauon Date: Fri, 10 May 2024 12:43:45 -0700 Subject: [PATCH 1/2] Add private_space_floating_mask_view flag. bug: 339850589 Test: In follow up CL Flag: ACONFIG com.android.launcher3.private_space_floating_mask_view DEVELOPMENT Change-Id: Idc03b9d3439046e6a1397599b33c7af9f53b1333 --- aconfig/launcher_search.aconfig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig index 31d8d348da..b243922a29 100644 --- a/aconfig/launcher_search.aconfig +++ b/aconfig/launcher_search.aconfig @@ -42,3 +42,11 @@ flag { description: "This flag disables drag and drop for Private Space Items." bug: "289223923" } + + +flag { + name: "private_space_floating_mask_view" + namespace: "launcher_search" + description: "This flag enables the floating mask view as part of the Private Space animation. " + bug: "339850589" +} From 1d3ddb877d3530342f239755584e39d7c2f56f4c Mon Sep 17 00:00:00 2001 From: Brandon Dayauon Date: Fri, 10 May 2024 13:06:43 -0700 Subject: [PATCH 2/2] Add floatingMaskView when animating to mimic bottom container. - On expand, we add the floating mask view and translate it out at the end. - On collapse, we translate off the mask view in the beginning once the floating mask view is added so that we can translate it in before the actual collapsing part of the animation bug:339850589 Test manually: https://drive.google.com/file/d/1YNc3vq9Cb5BcbcPOHp8H3lhe6KmYBdLI/view?usp=sharing Flag:ACONFIG com.android.launcher3.private_space_floating_mask_view STAGING Change-Id: I7c303e6629d83408bd314886fe10113246e44dcb --- res/drawable/bg_ps_mask_left_corner.xml | 30 +++++++ res/drawable/bg_ps_mask_right_corner.xml | 30 +++++++ res/layout/private_space_mask_view.xml | 55 +++++++++++++ res/values/dimens.xml | 2 + .../launcher3/allapps/FloatingMaskView.java | 65 +++++++++++++++ .../allapps/PrivateProfileManager.java | 80 +++++++++++++++++-- 6 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 res/drawable/bg_ps_mask_left_corner.xml create mode 100644 res/drawable/bg_ps_mask_right_corner.xml create mode 100644 res/layout/private_space_mask_view.xml create mode 100644 src/com/android/launcher3/allapps/FloatingMaskView.java diff --git a/res/drawable/bg_ps_mask_left_corner.xml b/res/drawable/bg_ps_mask_left_corner.xml new file mode 100644 index 0000000000..43eeedb0b4 --- /dev/null +++ b/res/drawable/bg_ps_mask_left_corner.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/bg_ps_mask_right_corner.xml b/res/drawable/bg_ps_mask_right_corner.xml new file mode 100644 index 0000000000..d63b866f9c --- /dev/null +++ b/res/drawable/bg_ps_mask_right_corner.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/layout/private_space_mask_view.xml b/res/layout/private_space_mask_view.xml new file mode 100644 index 0000000000..44e2797020 --- /dev/null +++ b/res/layout/private_space_mask_view.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 54edab469f..74fadda131 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -536,6 +536,8 @@ 8dp 6dp 10dp + 28dp + 16dp 136px diff --git a/src/com/android/launcher3/allapps/FloatingMaskView.java b/src/com/android/launcher3/allapps/FloatingMaskView.java new file mode 100644 index 0000000000..606eb0328e --- /dev/null +++ b/src/com/android/launcher3/allapps/FloatingMaskView.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.allapps; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.launcher3.R; +import com.android.launcher3.views.ActivityContext; + +public class FloatingMaskView extends ConstraintLayout { + + private final ActivityContext mActivityContext; + private ImageView mBottomBox; + + public FloatingMaskView(Context context) { + this(context, null, 0); + } + + public FloatingMaskView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FloatingMaskView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mActivityContext = ActivityContext.lookupContext(context); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mBottomBox = findViewById(R.id.bottom_box); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams(); + AllAppsRecyclerView allAppsContainerView = + mActivityContext.getAppsView().getActiveRecyclerView(); + if (lp != null) { + lp.rightMargin = allAppsContainerView.getPaddingRight(); + lp.leftMargin = allAppsContainerView.getPaddingLeft(); + mBottomBox.setMinimumHeight(allAppsContainerView.getPaddingBottom()); + } + } +} diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index ae0e80c50a..aa52feed9c 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -57,6 +57,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; @@ -93,14 +94,17 @@ public class PrivateProfileManager extends UserProfileManager { private static final int TEXT_UNLOCK_OPACITY_DURATION = 300; private static final int TEXT_LOCK_OPACITY_DURATION = 50; private static final int APP_OPACITY_DURATION = 400; + private static final int MASK_VIEW_DURATION = 200; private static final int APP_OPACITY_DELAY = 400; private static final int SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY = 400; private static final int SETTINGS_OPACITY_DELAY = 400; 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 final ActivityAllAppsContainerView mAllApps; private final Predicate mPrivateProfileMatcher; private final int mPsHeaderHeight; + private final int mFloatingMaskViewCornerRadius; private final RecyclerView.OnScrollListener mOnIdleScrollListener = new RecyclerView.OnScrollListener() { @Override @@ -120,6 +124,7 @@ public class PrivateProfileManager extends UserProfileManager { private Runnable mOnPSHeaderAdded; @Nullable private RelativeLayout mPSHeader; + private ConstraintLayout mFloatingMaskView; private final String mLockedStateContentDesc; private final String mUnLockedStateContentDesc; @@ -139,6 +144,8 @@ public class PrivateProfileManager extends UserProfileManager { .getString(R.string.ps_container_lock_button_content_description); mUnLockedStateContentDesc = mAllApps.getContext() .getString(R.string.ps_container_unlock_button_content_description); + mFloatingMaskViewCornerRadius = mAllApps.getContext().getResources().getDimensionPixelSize( + R.dimen.ps_floating_mask_corner_radius); } /** Adds Private Space Header to the layout. */ @@ -216,6 +223,7 @@ public class PrivateProfileManager extends UserProfileManager { .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED); int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED; setCurrentState(updatedState); + mFloatingMaskView = null; if (mPSHeader != null) { mPSHeader.setAlpha(1); } @@ -491,12 +499,15 @@ public class PrivateProfileManager extends UserProfileManager { RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager(); if (layoutManager != null) { startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller); - currentItem.decorationInfo = null; + // Preserve decorator if floating mask view exists. + if (mFloatingMaskView == null) { + currentItem.decorationInfo = null; + } } break; } // Make the private space apps gone to "collapse". - if (isPrivateSpaceItem(currentItem)) { + if (mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) { RecyclerView.ViewHolder viewHolder = allAppsRecyclerView.findViewHolderForAdapterPosition(i); if (viewHolder != null) { @@ -634,6 +645,7 @@ public class PrivateProfileManager extends UserProfileManager { setAnimationRunning(false); return; } + attachFloatingMaskView(expand); ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup); ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button); TextView lockText = lockButton.findViewById(R.id.lock_text); @@ -657,6 +669,11 @@ public class PrivateProfileManager extends UserProfileManager { lockText.setVisibility(expand ? VISIBLE : GONE); setAnimationRunning(true); } + + @Override + public void onAnimationEnd(Animator animation) { + detachFloatingMaskView(); + } }); animatorSet.addListener(forEndCallback(() -> { setAnimationRunning(false); @@ -671,13 +688,17 @@ public class PrivateProfileManager extends UserProfileManager { } })); if (expand) { - animatorSet.playTogether(animateAlphaOfIcons(true)); + animatorSet.playTogether(animateAlphaOfIcons(true), + translateFloatingMaskView(false)); } else { if (isPrivateSpaceHidden()) { - animatorSet.playSequentially(animateAlphaOfIcons(false), - animateCollapseAnimation(), fadeOutHeaderAlpha()); + animatorSet.playSequentially(translateFloatingMaskView(false), + animateAlphaOfIcons(false), + animateCollapseAnimation(), + fadeOutHeaderAlpha()); } else { - animatorSet.playSequentially(animateAlphaOfIcons(false), + animatorSet.playSequentially(translateFloatingMaskView(true), + animateAlphaOfIcons(false), animateCollapseAnimation()); } } @@ -705,6 +726,27 @@ public class PrivateProfileManager extends UserProfileManager { return alphaAnim; } + /** Fades out the private space container. */ + private ValueAnimator translateFloatingMaskView(boolean animateIn) { + if (!Flags.privateSpaceFloatingMaskView() || mFloatingMaskView == null) { + return new ValueAnimator(); + } + // Translate base on the height amount. Translates out on expand and in on collapse. + float floatingMaskViewHeight = getFloatingMaskViewHeight(); + float from = animateIn ? floatingMaskViewHeight : 0; + float to = animateIn ? 0 : floatingMaskViewHeight; + ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to); + alphaAnim.setDuration(MASK_VIEW_DURATION); + alphaAnim.setStartDelay(MASK_VIEW_DELAY); + alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue()); + } + }); + return alphaAnim; + } + /** Animates the layout changes when the text of the button becomes visible/gone. */ private void enableLayoutTransition(ViewGroup settingsAndLockGroup) { LayoutTransition settingsAndLockTransition = new LayoutTransition(); @@ -771,6 +813,28 @@ public class PrivateProfileManager extends UserProfileManager { }); } + private void attachFloatingMaskView(boolean expand) { + if (!Flags.privateSpaceFloatingMaskView()) { + return; + } + mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate( + R.layout.private_space_mask_view, mAllApps, false); + mAllApps.addView(mFloatingMaskView); + // Translate off the screen first if its collapsing so this header view isn't visible to + // user when animation starts. + if (!expand) { + mFloatingMaskView.setTranslationY(getFloatingMaskViewHeight()); + } + mFloatingMaskView.setVisibility(VISIBLE); + } + + private void detachFloatingMaskView() { + if (mFloatingMaskView != null) { + mAllApps.removeView(mFloatingMaskView); + } + mFloatingMaskView = null; + } + /** Starts the smooth scroll with the provided smoothScroller and add idle listener. */ private void startAnimationScroll(AllAppsRecyclerView allAppsRecyclerView, RecyclerView.LayoutManager layoutManager, RecyclerView.SmoothScroller smoothScroller) { @@ -780,6 +844,10 @@ public class PrivateProfileManager extends UserProfileManager { allAppsRecyclerView.addOnScrollListener(mOnIdleScrollListener); } + private float getFloatingMaskViewHeight() { + return mFloatingMaskViewCornerRadius + getMainRecyclerView().getPaddingBottom(); + } + AllAppsRecyclerView getMainRecyclerView() { return mAllApps.mAH.get(ActivityAllAppsContainerView.AdapterHolder.MAIN).mRecyclerView; }