Merge "Exposing functionality to pin Taskbar from TaskbarDividerPopupView." into udc-dev
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 ca29afb..738ff87 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);
@@ -249,6 +260,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;
@@ -385,6 +398,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 98d09e7..0714863 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