Exposing functionality to pin Taskbar from TaskbarDividerPopupView.

This CL allows user to long press on Taskbar divider view to bring up divider popup view. It also included functionality of allowing user to turn on always show taskbar from the divider popup view.

Test: Manual
Bug: 265436055
Bug: 265434718
Bug: 265434902
Bug: 265434705
Flag: ENABLE_TASKBAR_PINNING

Change-Id: Ied54d718483a9b06b053d68988e5c294a786002a
diff --git a/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
similarity index 67%
rename from res/layout/taskbar_divider.xml
rename to quickstep/res/layout/taskbar_divider.xml
index e25e7a3..73f3811 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.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/taskbar_divider_container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/taskbar_icon_min_touch_size"
+    android:layout_height="@dimen/taskbar_icon_min_touch_size"
+    android:contentDescription="@string/taskbar_divider_a11y_title"
+    android:backgroundTint="@android:color/transparent">
 
     <View
-        android:id="@+id/taskbar_divider_bar"
         android:layout_height="32dp"
         android:layout_width="2dp"
         android:layout_gravity="center"
-        android:background="@drawable/bg_rounded_corner_bottom_sheet_handle" />
-    <!-- TODO(b/265347148): Create separate drawable -->
-</FrameLayout>
+        android:background="@drawable/taskbar_divider_bg" />
+</FrameLayout>
\ 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 0000000..195443e
--- /dev/null
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<com.android.launcher3.taskbar.TaskbarDividerPopupView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/taskbar_pinning_popup_menu_width"
+    android:layout_height="wrap_content"
+    android:focusable="true"
+    android:background="@drawable/popup_background_material_u"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/taskbar_switch_option"
+        android:layout_width="match_parent"
+        android:layout_height="52dp"
+        android:layout_gravity="center_vertical"
+        android:elevation="2dp"
+        android:focusable="true"
+        android:clickable="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:background="@drawable/top_rounded_popup_ripple"
+        android:paddingEnd="10dp"
+        android:paddingStart="10dp"
+        android:theme="@style/PopupItem">
+
+        <View
+            android:layout_margin="6dp"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:background="@drawable/ic_visibility"
+            android:backgroundTint="?android:attr/textColorPrimary" />
+
+        <Switch
+            style="@style/BaseIcon"
+            android:id="@+id/taskbar_pinning_switch"
+            android:background="@null"
+            android:clickable="false"
+            android:gravity="start|center_vertical"
+            android:textAlignment="viewStart"
+            android:paddingStart="12dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textSize="14sp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:text="@string/always_show_taskbar" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/navigation_mode_switch_option"
+        android:layout_width="match_parent"
+        android:layout_height="52dp"
+        android:layout_gravity="center_vertical"
+        android:elevation="2dp"
+        android:clickable="true"
+        android:focusable="true"
+        android:background="@drawable/bottom_rounded_popup_ripple"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="10dp"
+        android:paddingStart="10dp"
+        android:theme="@style/PopupItem">
+
+        <View
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_margin="4dp"
+            android:background="@drawable/ic_touch"
+            android:backgroundTint="?android:attr/textColorPrimary" />
+
+        <com.android.launcher3.BubbleTextView
+            style="@style/BaseIcon"
+            android:id="@+id/change_navigation_mode_text"
+            android:gravity="start|center_vertical"
+            android:textAlignment="viewStart"
+            android:paddingStart="12dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textSize="14sp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:text="@string/change_navigation_mode" />
+
+    </LinearLayout>
+</com.android.launcher3.taskbar.TaskbarDividerPopupView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index cdb3b1c..959fea7 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -341,6 +341,9 @@
     <dimen name="taskbar_edu_features_lottie_height">106dp</dimen>
     <dimen name="taskbar_edu_features_horizontal_spacing">24dp</dimen>
 
+    <!--- Taskbar Pinning -->
+    <dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
+
     <!-- Recents overview -->
     <dimen name="recents_filter_icon_size">30dp</dimen>
 
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 2c17ce8..2b6f749 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -277,6 +277,13 @@
     <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
     <!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_phone_a11y_title">Navigation bar</string>
+    <!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
+    <string name="always_show_taskbar">Always show Taskbar</string>
+    <!-- Text in popup dialog for user to switch between system navigation modes. [CHAR LIMIT=30] -->
+    <string name="change_navigation_mode">Change navigation mode</string>
+    <!-- Accessibility title for the Taskbar vertical divider icon. [CHAR_LIMIT=NONE] -->
+    <string name="taskbar_divider_a11y_title">Taskbar Divider</string>
+
 
     <!-- Label for moving drop target to the top or left side of the screen, depending on orientation (from the Taskbar only). -->
     <string name="move_drop_target_top_or_left">Move to top&#47;left</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 809d715..9db03f5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -238,7 +238,8 @@
                         ? 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 8efb9b0..1cd6f50 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -60,6 +60,7 @@
     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 @@
             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 @@
         this.taskbarRecentAppsController = taskbarRecentAppsController;
         this.taskbarEduTooltipController = taskbarEduTooltipController;
         this.keyboardQuickSwitchController = keyboardQuickSwitchController;
+        this.taskbarPinningController = taskbarPinningController;
     }
 
     /**
@@ -163,6 +166,7 @@
         taskbarTranslationController.init(this);
         taskbarEduTooltipController.init(this);
         keyboardQuickSwitchController.init(this);
+        taskbarPinningController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -171,7 +175,7 @@
                 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 0000000..8dbc51a
--- /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 0000000..2000d98
--- /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<T : TaskbarActivityContext>
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+) : ArrowPopup<T>(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<LinearLayout>(R.id.taskbar_switch_option)
+        val alwaysShowTaskbarSwitch = findViewById<Switch>(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 3d8bf9e..d3c4128 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.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.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 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 @@
     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 @@
     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 @@
             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 69ea9fd..1d90a43 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -18,11 +18,12 @@
 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 @@
         // 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 @@
      * 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 6034739..f099e06 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -218,7 +218,8 @@
             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 6eb409e..ec3d1bc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -641,6 +641,13 @@
             };
         }
 
+        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 3bf4ad3..20466ad 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 @@
     @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 @@
                 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 0000000..739833a
--- /dev/null
+++ b/res/drawable/bottom_rounded_popup_ripple.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="#FFFFFFFF"/>
+            <corners android:bottomLeftRadius="@dimen/dialogCornerRadius"
+                android:bottomRightRadius="@dimen/dialogCornerRadius"
+                android:topLeftRadius="0dp"
+                android:topRightRadius="0dp"/>
+        </shape>
+    </item>
+</ripple>
\ 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 0000000..ea0e05c
--- /dev/null
+++ b/res/drawable/ic_touch.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18.19,12.44l-3.24,-1.62c1.29,-1 2.12,-2.56 2.12,-4.32c0,-3.03 -2.47,-5.5 -5.5,-5.5s-5.5,2.47 -5.5,5.5c0,2.13 1.22,3.98 3,4.89v3.26c-2.11,-0.45 -2.01,-0.44 -2.26,-0.44c-0.53,0 -1.03,0.21 -1.41,0.59L4,16.22l5.09,5.09C9.52,21.75 10.12,22 10.74,22h6.3c0.98,0 1.81,-0.7 1.97,-1.67l0.8,-4.71C20.03,14.32 19.38,13.04 18.19,12.44zM17.84,15.29L17.04,20h-6.3c-0.09,0 -0.17,-0.04 -0.24,-0.1l-3.68,-3.68l4.25,0.89V6.5c0,-0.28 0.22,-0.5 0.5,-0.5c0.28,0 0.5,0.22 0.5,0.5v6h1.76l3.46,1.73C17.69,14.43 17.91,14.86 17.84,15.29zM8.07,6.5c0,-1.93 1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5c0,0.95 -0.38,1.81 -1,2.44V6.5c0,-1.38 -1.12,-2.5 -2.5,-2.5c-1.38,0 -2.5,1.12 -2.5,2.5v2.44C8.45,8.31 8.07,7.45 8.07,6.5z"/>
+</vector>
diff --git a/res/drawable/ic_visibility.xml b/res/drawable/ic_visibility.xml
new file mode 100644
index 0000000..864de2e
--- /dev/null
+++ b/res/drawable/ic_visibility.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20">
+  <group>
+    <path
+        android:pathData="M10,5.833C7.933,5.833 6.25,7.517 6.25,9.583C6.25,11.65 7.933,13.333 10,13.333C12.067,13.333 13.75,11.65 13.75,9.583C13.75,7.517 12.067,5.833 10,5.833ZM10,11.833C8.758,11.833 7.75,10.825 7.75,9.583C7.75,8.342 8.758,7.333 10,7.333C11.242,7.333 12.25,8.342 12.25,9.583C12.25,10.825 11.242,11.833 10,11.833Z"
+        android:fillColor="#191C1D"/>
+    <path
+        android:pathData="M10.001,3.333C5.834,3.333 2.276,5.925 0.834,9.583C2.276,13.242 5.834,15.833 10.001,15.833C14.167,15.833 17.726,13.242 19.167,9.583C17.726,5.925 14.167,3.333 10.001,3.333ZM10.001,14.167C6.842,14.167 4.026,12.392 2.651,9.583C4.026,6.775 6.842,5 10.001,5C13.159,5 15.976,6.775 17.351,9.583C15.976,12.392 13.159,14.167 10.001,14.167Z"
+        android:fillColor="#191C1D"/>
+  </group>
+</vector>
diff --git a/res/drawable/taskbar_divider_bg.xml b/res/drawable/taskbar_divider_bg.xml
new file mode 100644
index 0000000..a8c2ae7
--- /dev/null
+++ b/res/drawable/taskbar_divider_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle" >
+    <solid android:color="?androidprv:attr/colorSurfaceVariant"/>
+    <corners android:radius="1dp" />
+</shape>
diff --git a/res/drawable/top_rounded_popup_ripple.xml b/res/drawable/top_rounded_popup_ripple.xml
new file mode 100644
index 0000000..7468480
--- /dev/null
+++ b/res/drawable/top_rounded_popup_ripple.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="#FFFFFFFF"/>
+            <corners android:bottomLeftRadius="0dp"
+                android:bottomRightRadius="0dp"
+                android:topLeftRadius="@dimen/dialogCornerRadius"
+                android:topRightRadius="@dimen/dialogCornerRadius"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
index 008a77c..932ce38 100644
--- a/res/values-v31/styles.xml
+++ b/res/values-v31/styles.xml
@@ -24,7 +24,7 @@
         <item name="android:listPreferredItemPaddingStart">24dp</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:switchStyle">@style/HomeSettings.SwitchStyle</item>
+        <item name="android:switchStyle">@style/SwitchStyle</item>
         <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
@@ -61,13 +61,6 @@
         <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
     </style>
 
-    <style name="HomeSettings.SwitchStyle"
-            parent="@android:style/Widget.Material.CompoundButton.Switch">
-        <item name="android:switchMinWidth">52dp</item>
-        <item name="android:thumb">@drawable/home_settings_switch_thumb</item>
-        <item name="android:track">@drawable/home_settings_switch_track</item>
-    </style>
-
     <style name="HomeSettings.PreferenceTitle"
             parent="@android:style/TextAppearance.Material.Subhead">
         <item name="android:fontFamily">google-sans</item>
diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml
index bd48468..1261b23 100644
--- a/res/values-v33/style.xml
+++ b/res/values-v33/style.xml
@@ -23,7 +23,7 @@
         <item name="android:listPreferredItemPaddingStart">24dp</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:switchStyle">@style/HomeSettings.SwitchStyle</item>
+        <item name="android:switchStyle">@style/SwitchStyle</item>
         <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5dc4f0a..f8d01eb 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -65,12 +65,19 @@
         <item name="overviewScrimColor">@color/overview_scrim</item>
         <item name="preloadIconAccentColor">@color/preload_icon_accent_color_light</item>
         <item name="preloadIconBackgroundColor">@color/preload_icon_background_color_light</item>
-
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
         <item name="android:statusBarColor">#00000000</item>
         <item name="android:navigationBarColor">#00000000</item>
+        <item name="android:switchStyle">@style/SwitchStyle</item>
+    </style>
+
+    <style name="SwitchStyle"
+        parent="@android:style/Widget.Material.CompoundButton.Switch">
+        <item name="android:switchMinWidth">52dp</item>
+        <item name="android:thumb">@drawable/home_settings_switch_thumb</item>
+        <item name="android:track">@drawable/home_settings_switch_track</item>
     </style>
 
     <style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme" />
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 29f4a62..31f9bfe 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -74,7 +74,8 @@
             TYPE_TASKBAR_EDUCATION_DIALOG,
             TYPE_TASKBAR_ALL_APPS,
             TYPE_ADD_TO_HOME_CONFIRMATION,
-            TYPE_TASKBAR_OVERLAY_PROXY
+            TYPE_TASKBAR_OVERLAY_PROXY,
+            TYPE_TASKBAR_PINNING_POPUP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -102,6 +103,7 @@
     public static final int TYPE_TASKBAR_ALL_APPS = 1 << 18;
     public static final int TYPE_ADD_TO_HOME_CONFIRMATION = 1 << 19;
     public static final int TYPE_TASKBAR_OVERLAY_PROXY = 1 << 20;
+    public static final int TYPE_TASKBAR_PINNING_POPUP = 1 << 21;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
@@ -110,7 +112,7 @@
             | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
             | TYPE_WIDGETS_EDUCATION_DIALOG | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS
             | TYPE_OPTIONS_POPUP_DIALOG | TYPE_ADD_TO_HOME_CONFIRMATION
-            | TYPE_TASKBAR_OVERLAY_PROXY;
+            | TYPE_TASKBAR_OVERLAY_PROXY | TYPE_TASKBAR_PINNING_POPUP;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index fb4da0c..c98df1b 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -273,12 +273,15 @@
 
         @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
 
+        const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
         @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
         @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
         @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
         @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true)
         @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true)
+        @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false)
+
         @JvmField
         val DEVICE_TYPE =
             backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 02ebb15..776fe40 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -19,9 +19,10 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
 import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRANSIENT_TASKBAR;
-import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
@@ -45,6 +46,7 @@
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
@@ -101,9 +103,12 @@
     private Info mInfo;
     private boolean mDestroyed = false;
 
+    private final LauncherPrefs mPrefs;
+
     private DisplayController(Context context) {
         mContext = context;
         mDM = context.getSystemService(DisplayManager.class);
+        mPrefs = LauncherPrefs.get(context);
 
         Display display = mDM.getDisplay(DEFAULT_DISPLAY);
         if (Utilities.ATLEAST_S) {
@@ -144,7 +149,9 @@
         // TODO(b/258604917): When running in test harness, use !sTransientTaskbarStatusForTests
         //  once tests are updated to expect new persistent behavior such as not allowing long press
         //  to stash.
-        if (!Utilities.isRunningInTestHarness() && FORCE_PERSISTENT_TASKBAR.get()) {
+        if (!Utilities.isRunningInTestHarness()
+                && ENABLE_TASKBAR_PINNING.get()
+                && mPrefs.get(TASKBAR_PINNING)) {
             return false;
         }
         return getInfo().navigationMode == NavigationMode.NO_BUTTON