diff --git a/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml similarity index 66% rename from res/layout/taskbar_divider.xml rename to quickstep/res/layout/taskbar_divider.xml index e25e7a30c1..73f3811ecf 100644 --- a/res/layout/taskbar_divider.xml +++ b/quickstep/res/layout/taskbar_divider.xml @@ -13,16 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. --> - + - - + android:background="@drawable/taskbar_divider_bg" /> + \ No newline at end of file diff --git a/quickstep/res/layout/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml new file mode 100644 index 0000000000..195443ead9 --- /dev/null +++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index cdb3b1ccb8..959fea7572 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -341,6 +341,9 @@ 106dp 24dp + + 300dp + 30dp diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 2c17ce8686..2b6f74945f 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -277,6 +277,13 @@ Taskbar hidden Navigation bar + + Always show Taskbar + + Change navigation mode + + Taskbar Divider + Move to top/left diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 809d715260..9db03f53a6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -238,7 +238,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { ? new DesktopTaskbarRecentAppsController(this) : TaskbarRecentAppsController.DEFAULT, new TaskbarEduTooltipController(this), - new KeyboardQuickSwitchController()); + new KeyboardQuickSwitchController(), + new TaskbarDividerPopupController(this)); } public void init(@NonNull TaskbarSharedState sharedState) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index 8efb9b0e47..1cd6f50d33 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -60,6 +60,7 @@ public class TaskbarControllers { public final TaskbarOverlayController taskbarOverlayController; public final TaskbarEduTooltipController taskbarEduTooltipController; public final KeyboardQuickSwitchController keyboardQuickSwitchController; + public final TaskbarDividerPopupController taskbarPinningController; @Nullable private LoggableTaskbarController[] mControllersToLog = null; @Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null; @@ -105,7 +106,8 @@ public class TaskbarControllers { TaskbarSpringOnStashController taskbarSpringOnStashController, TaskbarRecentAppsController taskbarRecentAppsController, TaskbarEduTooltipController taskbarEduTooltipController, - KeyboardQuickSwitchController keyboardQuickSwitchController) { + KeyboardQuickSwitchController keyboardQuickSwitchController, + TaskbarDividerPopupController taskbarPinningController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -130,6 +132,7 @@ public class TaskbarControllers { this.taskbarRecentAppsController = taskbarRecentAppsController; this.taskbarEduTooltipController = taskbarEduTooltipController; this.keyboardQuickSwitchController = keyboardQuickSwitchController; + this.taskbarPinningController = taskbarPinningController; } /** @@ -163,6 +166,7 @@ public class TaskbarControllers { taskbarTranslationController.init(this); taskbarEduTooltipController.init(this); keyboardQuickSwitchController.init(this); + taskbarPinningController.init(this); mControllersToLog = new LoggableTaskbarController[] { taskbarDragController, navButtonController, navbarButtonsViewController, @@ -171,7 +175,7 @@ public class TaskbarControllers { stashedHandleViewController, taskbarStashController, taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController, voiceInteractionWindowController, taskbarTranslationController, - taskbarEduTooltipController, keyboardQuickSwitchController + taskbarEduTooltipController, keyboardQuickSwitchController, taskbarPinningController }; mBackgroundRendererControllers = new BackgroundRendererController[] { taskbarDragLayerController, taskbarScrimViewController, diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt new file mode 100644 index 0000000000..8dbc51af7d --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 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.taskbar + +import android.view.View +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING +import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate +import java.io.PrintWriter + +/** Controls taskbar pinning through a popup view. */ +class TaskbarDividerPopupController(private val context: TaskbarActivityContext) : + TaskbarControllers.LoggableTaskbarController { + + private lateinit var controllers: TaskbarControllers + private val launcherPrefs = LauncherPrefs.get(context) + + fun init(taskbarControllers: TaskbarControllers) { + controllers = taskbarControllers + } + + fun showPinningView(view: View) { + context.isTaskbarWindowFullscreen = true + + view.post { + val popupView = createAndPopulate(view, context) + popupView.requestFocus() + popupView.onCloseCallback = { + context.onPopupVisibilityChanged(false) + if (launcherPrefs.get(TASKBAR_PINNING)) { + animateTransientToPersistentTaskBar() + } else { + animatePersistentToTransientTaskbar() + } + } + popupView.changePreference = { + launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING)) + } + context.onPopupVisibilityChanged(true) + popupView.show() + } + } + + // TODO(b/265436799): provide animation/transition from transient taskbar to persistent one + private fun animateTransientToPersistentTaskBar() {} + + // TODO(b/265436799): provide animation/transition from persistent taskbar to transient one + private fun animatePersistentToTransientTaskbar() {} + + override fun dumpLogs(prefix: String, pw: PrintWriter) { + pw.println(prefix + "TaskbarPinningController:") + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt new file mode 100644 index 0000000000..2000d984c4 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2023 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.taskbar + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.widget.LinearLayout +import android.widget.Switch +import androidx.core.view.postDelayed +import com.android.launcher3.R +import com.android.launcher3.popup.ArrowPopup +import com.android.launcher3.popup.RoundedArrowDrawable +import com.android.launcher3.util.DisplayController +import com.android.launcher3.util.Themes + +/** Popup view with arrow for taskbar pinning */ +class TaskbarDividerPopupView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : ArrowPopup(context, attrs, defStyleAttr) { + companion object { + private const val TAG = "TaskbarDividerPopupView" + private const val DIVIDER_POPUP_CLOSING_DELAY = 500L + + @JvmStatic + fun createAndPopulate( + view: View, + taskbarActivityContext: TaskbarActivityContext, + ): TaskbarDividerPopupView<*> { + val taskMenuViewWithArrow = + taskbarActivityContext.layoutInflater.inflate( + R.layout.taskbar_divider_popup_menu, + taskbarActivityContext.dragLayer, + false + ) as TaskbarDividerPopupView<*> + + return taskMenuViewWithArrow.populateForView(view) + } + } + private lateinit var dividerView: View + + private val menuWidth = + context.resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width) + private val popupCornerRadius = Themes.getDialogCornerRadius(context) + private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width) + private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height) + private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius) + + private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context) + private var didPreferenceChange = false + + /** Callback invoked when the pinning popup view is closing. */ + var onCloseCallback: () -> Unit = {} + + /** + * Callback invoked when the user preference changes in popup view. Preference change will be + * based upon current value stored in [LauncherPrefs] for `TASKBAR_PINNING` + */ + var changePreference: () -> Unit = {} + + init { + // This synchronizes the arrow and menu to open at the same time + mOpenChildFadeStartDelay = mOpenFadeStartDelay + mOpenChildFadeDuration = mOpenFadeDuration + mCloseFadeStartDelay = mCloseChildFadeStartDelay + mCloseFadeDuration = mCloseChildFadeDuration + } + + override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_PINNING_POPUP != 0 + + override fun getTargetObjectLocation(outPos: Rect) { + popupContainer.getDescendantRectRelativeToSelf(dividerView, outPos) + } + + @SuppressLint("UseSwitchCompatOrMaterialCode") + override fun onFinishInflate() { + super.onFinishInflate() + val taskbarSwitchOption = findViewById(R.id.taskbar_switch_option) + val alwaysShowTaskbarSwitch = findViewById(R.id.taskbar_pinning_switch) + alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn + taskbarSwitchOption.setOnClickListener { + alwaysShowTaskbarSwitch.isClickable = true + alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn + onClickAlwaysShowTaskbarSwitchOption() + } + } + + /** Orient object as usual and then center object horizontally. */ + override fun orientAboutObject() { + super.orientAboutObject() + x = mTempRect.centerX() - menuWidth / 2f + } + + override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean { + if (ev?.action == MotionEvent.ACTION_DOWN) { + if (!popupContainer.isEventOverView(this, ev)) { + close(true) + } + } else if (popupContainer.isEventOverView(dividerView, ev)) { + return true + } + return false + } + + private fun populateForView(view: View): TaskbarDividerPopupView<*> { + dividerView = view + return this + } + + override fun addArrow() { + super.addArrow() + // Change arrow location to the middle of popup. + mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2) + } + + override fun updateArrowColor() { + if (!Gravity.isVertical(mGravity)) { + mArrow.background = + RoundedArrowDrawable( + arrowWidth, + arrowHeight, + arrowPointRadius, + popupCornerRadius, + measuredWidth.toFloat(), + measuredHeight.toFloat(), + (measuredWidth - arrowWidth) / 2, // arrowOffsetX + 0f, // arrowOffsetY + false, // isPointingUp + true, // leftAligned + Themes.getAttrColor(context, R.attr.popupColorPrimary), + ) + elevation = mElevation + mArrow.elevation = mElevation + } + } + + override fun closeComplete() { + if (didPreferenceChange) { + onCloseCallback() + } + super.closeComplete() + } + + private fun onClickAlwaysShowTaskbarSwitchOption() { + didPreferenceChange = true + changePreference() + // Allow switch animation to finish and then close the popup. + postDelayed(DIVIDER_POPUP_CLOSING_DELAY) { close(true) } + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index 3d8bf9ea63..d3c4128581 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -20,6 +20,8 @@ import static android.content.pm.PackageManager.FEATURE_PC; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING; +import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY; import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG; @@ -32,6 +34,7 @@ import android.content.ComponentCallbacks; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.hardware.display.DisplayManager; @@ -48,6 +51,7 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider; @@ -133,6 +137,13 @@ public class TaskbarManager { private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver = new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast); + private final SharedPreferences.OnSharedPreferenceChangeListener + mTaskbarPinningPreferenceChangeListener = (sharedPreferences, key) -> { + if (TASKBAR_PINNING_KEY.equals(key)) { + recreateTaskbar(); + } + }; + @SuppressLint("WrongConstant") public TaskbarManager(TouchInteractionService service) { mDisplayController = DisplayController.INSTANCE.get(service); @@ -244,6 +255,8 @@ public class TaskbarManager { private void destroyExistingTaskbar() { debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext); if (mTaskbarActivityContext != null) { + LauncherPrefs.get(mContext).removeListener(mTaskbarPinningPreferenceChangeListener, + TASKBAR_PINNING); mTaskbarActivityContext.onDestroy(); if (!FLAG_HIDE_NAVBAR_WINDOW) { mTaskbarActivityContext = null; @@ -380,6 +393,10 @@ public class TaskbarManager { mTaskbarActivityContext.setUIController( createTaskbarUIControllerForActivity(mActivity)); } + + // We to wait until user unlocks the device to attach listener. + LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener, + TASKBAR_PINNING); } public void onSystemUiFlagsChanged(int systemUiStateFlags) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 69ea9fd03a..1d90a43111 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -18,11 +18,12 @@ package com.android.launcher3.taskbar; import static android.view.HapticFeedbackConstants.LONG_PRESS; import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; +import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY; import static com.android.launcher3.anim.Interpolators.EMPHASIZED; import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR; +import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE; @@ -324,7 +325,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba // that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false). boolean isManuallyStashedInApp = supportsVisualStashing() && !isTransientTaskbar - && !FORCE_PERSISTENT_TASKBAR.get() + && !ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF); boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible; updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp); @@ -353,7 +354,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * Returns whether the user can manually stash the taskbar based on the current device state. */ protected boolean supportsManualStashing() { - if (FORCE_PERSISTENT_TASKBAR.get()) { + if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) { return false; } return supportsVisualStashing() diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 603473945c..f099e0636f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -218,7 +218,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener()); } if (mTaskbarDivider != null) { - //TODO(b/265434705): set long press listener + mTaskbarDivider.setOnLongClickListener( + mControllerCallbacks.getTaskbarDividerLongClickListener()); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 6eb409e35d..ec3d1bcc0a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -641,6 +641,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar }; } + public View.OnLongClickListener getTaskbarDividerLongClickListener() { + return v -> { + mControllers.taskbarPinningController.showPinningView(v); + return true; + }; + } + public View.OnLongClickListener getIconOnLongClickListener() { return mControllers.taskbarDragController::startDragOnLongClick; } diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt index 3bf4ad38c3..20466ad844 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt @@ -53,6 +53,7 @@ abstract class TaskbarBaseTestCase { @Mock lateinit var taskbarOverlayController: TaskbarOverlayController @Mock lateinit var taskbarEduTooltipController: TaskbarEduTooltipController @Mock lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController + @Mock lateinit var taskbarPinningController: TaskbarDividerPopupController lateinit var taskbarControllers: TaskbarControllers @@ -91,7 +92,8 @@ abstract class TaskbarBaseTestCase { taskbarSpringOnStashController, taskbarRecentAppsController, taskbarEduTooltipController, - keyboardQuickSwitchController + keyboardQuickSwitchController, + taskbarPinningController, ) } } diff --git a/res/drawable/bottom_rounded_popup_ripple.xml b/res/drawable/bottom_rounded_popup_ripple.xml new file mode 100644 index 0000000000..739833a47c --- /dev/null +++ b/res/drawable/bottom_rounded_popup_ripple.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_touch.xml b/res/drawable/ic_touch.xml new file mode 100644 index 0000000000..ea0e05c547 --- /dev/null +++ b/res/drawable/ic_touch.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/drawable/ic_visibility.xml b/res/drawable/ic_visibility.xml new file mode 100644 index 0000000000..864de2ec18 --- /dev/null +++ b/res/drawable/ic_visibility.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/res/drawable/taskbar_divider_bg.xml b/res/drawable/taskbar_divider_bg.xml new file mode 100644 index 0000000000..a8c2ae7032 --- /dev/null +++ b/res/drawable/taskbar_divider_bg.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/res/drawable/top_rounded_popup_ripple.xml b/res/drawable/top_rounded_popup_ripple.xml new file mode 100644 index 0000000000..7468480192 --- /dev/null +++ b/res/drawable/top_rounded_popup_ripple.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml index 008a77c3a8..932ce38e59 100644 --- a/res/values-v31/styles.xml +++ b/res/values-v31/styles.xml @@ -24,7 +24,7 @@ 24dp @android:color/transparent @android:color/transparent - @style/HomeSettings.SwitchStyle + @style/SwitchStyle @style/HomeSettings.PreferenceTitle false true @@ -61,13 +61,6 @@ @bool/home_settings_icon_space_reserved - - + +