diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml index 81b28ba004..c116c12bad 100644 --- a/res/layout/work_mode_fab.xml +++ b/res/layout/work_mode_fab.xml @@ -12,24 +12,35 @@ See the License for the specific language governing permissions and limitations under the License. --> - \ No newline at end of file + android:contentDescription="@string/work_apps_pause_btn_text" + android:animateLayoutChanges="true"> + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index d66c82496e..b57eb02292 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -149,6 +149,8 @@ 56dp 16dp + 24dp + 8dp 10dp 52dp 16dp diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java index 74316e22d5..ca08164a05 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java @@ -575,6 +575,10 @@ public abstract class BaseAllAppsContainerView { @@ -666,10 +670,10 @@ public abstract class BaseAllAppsContainerView mAH.get(AdapterHolder.WORK).applyPadding()); - } + + mWorkManager.reset(); + post(() -> mAH.get(AdapterHolder.WORK).applyPadding()); + } else { mWorkManager.detachWorkModeSwitch(); mViewPager = null; diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index aadd0b52de..c9466a8f43 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -15,17 +15,19 @@ */ package com.android.launcher3.allapps; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TURN_OFF_WORK_APPS_TAP; import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth; +import android.animation.LayoutTransition; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; -import android.view.ViewGroup.MarginLayoutParams; import android.view.WindowInsets; -import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.core.graphics.Insets; import androidx.core.view.WindowInsetsCompat; @@ -37,55 +39,62 @@ import com.android.launcher3.anim.KeyboardInsetAnimationCallback; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.StringCache; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip; - /** * Work profile toggle switch shown at the bottom of AllApps work tab */ -public class WorkModeSwitch extends Button implements Insettable, View.OnClickListener, - KeyboardInsetAnimationCallback.KeyboardInsetListener, - PersonalWorkSlidingTabStrip.OnActivePageChangedListener { +public class WorkModeSwitch extends LinearLayout implements Insettable, + KeyboardInsetAnimationCallback.KeyboardInsetListener { private static final int FLAG_FADE_ONGOING = 1 << 1; private static final int FLAG_TRANSLATION_ONGOING = 1 << 2; private static final int FLAG_PROFILE_TOGGLE_ONGOING = 1 << 3; + private static final int SCROLL_THRESHOLD_DP = 10; private final Rect mInsets = new Rect(); private final Rect mImeInsets = new Rect(); private int mFlags; - private boolean mWorkEnabled; - private boolean mOnWorkTab; + private final ActivityContext mActivityContext; - public WorkModeSwitch(Context context) { + // Threshold when user scrolls up/down to determine when should button extend/collapse + private final int mScrollThreshold; + private ImageView mIcon; + private TextView mTextView; + + public WorkModeSwitch(@NonNull Context context) { this(context, null, 0); } - public WorkModeSwitch(Context context, AttributeSet attrs) { + public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs) { this(context, attrs, 0); } - public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) { + public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP); + mActivityContext = ActivityContext.lookupContext(getContext()); } @Override protected void onFinishInflate() { super.onFinishInflate(); + + mIcon = findViewById(R.id.work_icon); + mTextView = findViewById(R.id.pause_text); setSelected(true); - setOnClickListener(this); if (Utilities.ATLEAST_R) { KeyboardInsetAnimationCallback keyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this); setWindowInsetsAnimationCallback(keyboardInsetAnimationCallback); } - ActivityContext activityContext = ActivityContext.lookupContext(getContext()); - DeviceProfile grid = activityContext.getDeviceProfile(); - setInsets(grid.getInsets()); - StringCache cache = activityContext.getStringCache(); + setInsets(mActivityContext.getDeviceProfile().getInsets()); + StringCache cache = mActivityContext.getStringCache(); if (cache != null) { - setText(cache.workProfilePauseButton); + mTextView.setText(cache.workProfilePauseButton); } + + mIcon.setColorFilter(mTextView.getCurrentTextColor()); + getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); } @Override @@ -102,8 +111,6 @@ public class WorkModeSwitch extends Button implements Insettable, View.OnClickLi if (!dp.isGestureMode && dp.isTaskbarPresent) { bottomMargin += dp.taskbarSize; - } else { - bottomMargin += insets.bottom; } lp.bottomMargin = bottomMargin; @@ -113,58 +120,32 @@ public class WorkModeSwitch extends Button implements Insettable, View.OnClickLi @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile(); View parent = (View) getParent(); + int allAppsLeftRightPadding = mActivityContext.getDeviceProfile().allAppsLeftRightPadding; int size = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight() - - 2 * dp.allAppsLeftRightPadding; + - 2 * allAppsLeftRightPadding; int tabWidth = getTabWidth(getContext(), size); - int shift = (size - tabWidth) / 2 + dp.allAppsLeftRightPadding; + int shift = (size - tabWidth) / 2 + allAppsLeftRightPadding; setTranslationX(Utilities.isRtl(getResources()) ? shift : -shift); } - @Override - public void onActivePageChanged(int page) { - mOnWorkTab = page == ActivityAllAppsContainerView.AdapterHolder.WORK; - updateVisibility(); - } - - @Override - public void onClick(View view) { - if (Utilities.ATLEAST_P && isEnabled()) { - setFlag(FLAG_PROFILE_TOGGLE_ONGOING); - ActivityContext activityContext = ActivityContext.lookupContext(getContext()); - activityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_OFF_WORK_APPS_TAP); - activityContext.getAppsView().getWorkManager().setWorkProfileEnabled(false); - } - } - @Override public boolean isEnabled() { return super.isEnabled() && getVisibility() == VISIBLE && mFlags == 0; } - /** - * Sets the enabled or disabled state of the button - */ - public void updateCurrentState(boolean isEnabled) { - removeFlag(FLAG_PROFILE_TOGGLE_ONGOING); - if (mWorkEnabled != isEnabled) { - mWorkEnabled = isEnabled; - updateVisibility(); - } - } - - private void updateVisibility() { + public void animateVisibility(boolean visible) { clearAnimation(); - if (mWorkEnabled && mOnWorkTab) { + if (visible) { setFlag(FLAG_FADE_ONGOING); setVisibility(VISIBLE); + extend(); animate().alpha(1).withEndAction(() -> removeFlag(FLAG_FADE_ONGOING)).start(); } else if (getVisibility() != GONE) { setFlag(FLAG_FADE_ONGOING); animate().alpha(0).withEndAction(() -> { removeFlag(FLAG_FADE_ONGOING); - this.setVisibility(GONE); + setVisibility(GONE); }).start(); } } @@ -213,4 +194,16 @@ public class WorkModeSwitch extends Button implements Insettable, View.OnClickLi private void removeFlag(int flag) { mFlags &= ~flag; } + + public void extend() { + mTextView.setVisibility(VISIBLE); + } + + public void shrink(){ + mTextView.setVisibility(GONE); + } + + public int getScrollThreshold() { + return mScrollThreshold; + } } diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java index cfac985c2d..547b74c833 100644 --- a/src/com/android/launcher3/allapps/WorkProfileManager.java +++ b/src/com/android/launcher3/allapps/WorkProfileManager.java @@ -17,6 +17,10 @@ package com.android.launcher3.allapps; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD; +import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.MAIN; +import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.SEARCH; +import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.WORK; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TURN_OFF_WORK_APPS_TAP; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; @@ -28,14 +32,19 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import android.view.View; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.views.ActivityContext; import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip; import java.lang.annotation.Retention; @@ -104,8 +113,16 @@ public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActiveP @Override public void onActivePageChanged(int page) { + updateWorkFAB(page); + } + + private void updateWorkFAB(int page) { if (mWorkModeSwitch != null) { - mWorkModeSwitch.onActivePageChanged(page); + if (page == MAIN || page == SEARCH) { + mWorkModeSwitch.animateVisibility(false); + } else if (page == WORK && mCurrentState == STATE_ENABLED) { + mWorkModeSwitch.animateVisibility(true); + } } } @@ -123,7 +140,12 @@ public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActiveP getAH().mAppsList.updateAdapterItems(); } if (mWorkModeSwitch != null) { - mWorkModeSwitch.updateCurrentState(currentState == STATE_ENABLED); + updateWorkFAB(mAllApps.getCurrentPage()); + } + if (mCurrentState == STATE_ENABLED) { + attachWorkModeSwitch(); + } else if (mCurrentState == STATE_DISABLED) { + detachWorkModeSwitch(); } } @@ -140,13 +162,16 @@ public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActiveP mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate( R.layout.work_mode_fab, mAllApps, false); } - if (mWorkModeSwitch.getParent() != mAllApps) { + if (mWorkModeSwitch.getParent() == null) { mAllApps.addView(mWorkModeSwitch); } + if (mAllApps.getCurrentPage() != WORK) { + mWorkModeSwitch.animateVisibility(false); + } if (getAH() != null) { getAH().applyPadding(); } - mWorkModeSwitch.updateCurrentState(mCurrentState == STATE_ENABLED); + mWorkModeSwitch.setOnClickListener(this::onWorkFabClicked); return true; } /** @@ -169,7 +194,7 @@ public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActiveP } private BaseAllAppsContainerView.AdapterHolder getAH() { - return mAllApps.mAH.get(BaseAllAppsContainerView.AdapterHolder.WORK); + return mAllApps.mAH.get(WORK); } public int getCurrentState() { @@ -199,4 +224,40 @@ public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActiveP private boolean isEduSeen() { return mPreferences.getInt(KEY_WORK_EDU_STEP, 0) != 0; } + + private void onWorkFabClicked(View view) { + if (Utilities.ATLEAST_P && mCurrentState == STATE_ENABLED && mWorkModeSwitch.isEnabled()) { + ActivityContext activityContext = ActivityContext.lookupContext( + mWorkModeSwitch.getContext()); + activityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_OFF_WORK_APPS_TAP); + setWorkProfileEnabled(false); + } + } + + public RecyclerView.OnScrollListener newScrollListener() { + return new RecyclerView.OnScrollListener() { + int totalDelta = 0; + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){ + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + totalDelta = 0; + } + } + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + WorkModeSwitch fab = getWorkModeSwitch(); + if (fab == null){ + return; + } + totalDelta = Utilities.boundToRange(totalDelta, + -fab.getScrollThreshold(), fab.getScrollThreshold()) + dy; + boolean isScrollAtTop = recyclerView.computeVerticalScrollOffset() == 0; + if ((isScrollAtTop || totalDelta < -fab.getScrollThreshold())) { + fab.extend(); + } else if (totalDelta > fab.getScrollThreshold()) { + fab.shrink(); + } + } + }; + } } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index bb3b939550..b46e43f63b 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -92,6 +92,10 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER", true, "Hide header on keyboard before typing in all apps"); + public static final BooleanFlag ENABLE_EXPANDING_PAUSE_WORK_BUTTON = new DeviceFlag( + "ENABLE_EXPANDING_PAUSE_WORK_BUTTON", false, + "Expand and collapse pause work button while scrolling"); + public static final BooleanFlag ENABLE_HIDE_HEADER_STATIC = new DeviceFlag( "ENABLE_HIDE_HEADER_STATIC", false, "Hide keyboard suggestion strip");