Adding Private Space views to Launcher.

This CL adds the following:
* Static View Elements to be added to AllApps recycler View
* View Controller to load the above elements dynamically
* Private Space Section Decorator
* PrivateProfile Manager containing the logic related to Private Space
* Abstract UserProfileManager as the super class of Work/Private
ProfileManager

Private Space Views Figma
[link](https://www.figma.com/file/K6bIIcG882EiJNjxvSWsFT/V%E2%80%A2-Private-Space?type=design&node-id=14535-111985&mode=design&t=JLz9W0O551TpzQYH-0)

Flag: ACONFIG com.android.launcher3.Flags.enable_private_space DEVELOPMENT
Bug: 289223923
Test: Ran Launcher3 tests
Change-Id: I8aa4247c78064a551e5e0d0b46d3fc033873f99d
diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml
new file mode 100644
index 0000000..526bb5a
--- /dev/null
+++ b/res/drawable/bg_ps_header.xml
@@ -0,0 +1,22 @@
+<?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"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/ps_container_corner_radius" />
+    <solid android:color="?attr/materialColorSurfaceContainerHigh" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_lock_button.xml b/res/drawable/bg_ps_lock_button.xml
new file mode 100644
index 0000000..aef1e81
--- /dev/null
+++ b/res/drawable/bg_ps_lock_button.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ 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="@dimen/ps_lock_button_width"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="89"
+    android:viewportHeight="36">
+    <path
+        android:pathData="M18,0L71,0A18,18 0,0 1,89 18L89,18A18,18 0,0 1,71 36L18,36A18,18 0,0 1,0 18L0,18A18,18 0,0 1,18 0z"
+        android:fillColor="?attr/materialColorPrimaryFixedDim"/>
+    <path
+        android:pathData="M26.167,14.667H27C27.917,14.667 28.667,15.417 28.667,16.333V24.667C28.667,25.583 27.917,26.333 27,26.333H17C16.083,26.333 15.333,25.583 15.333,24.667V16.333C15.333,15.417 16.083,14.667 17,14.667H17.833V13C17.833,10.7 19.7,8.833 22,8.833C24.3,8.833 26.167,10.7 26.167,13V14.667ZM22,10.5C20.617,10.5 19.5,11.617 19.5,13V14.667H24.5V13C24.5,11.617 23.383,10.5 22,10.5ZM17,24.667V16.333H27V24.667H17ZM23.667,20.5C23.667,21.417 22.917,22.167 22,22.167C21.083,22.167 20.333,21.417 20.333,20.5C20.333,19.583 21.083,18.833 22,18.833C22.917,18.833 23.667,19.583 23.667,20.5Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="M41.204,23V12.976H42.73V21.544H47.504V23H41.204ZM52.352,23.224C51.615,23.224 50.976,23.061 50.434,22.734C49.893,22.398 49.473,21.936 49.174,21.348C48.885,20.76 48.74,20.083 48.74,19.318C48.74,18.543 48.885,17.867 49.174,17.288C49.473,16.7 49.893,16.243 50.434,15.916C50.976,15.58 51.615,15.412 52.352,15.412C53.099,15.412 53.738,15.58 54.27,15.916C54.812,16.243 55.227,16.7 55.516,17.288C55.815,17.867 55.964,18.543 55.964,19.318C55.964,20.083 55.815,20.76 55.516,21.348C55.227,21.936 54.812,22.398 54.27,22.734C53.738,23.061 53.099,23.224 52.352,23.224ZM52.352,21.838C52.772,21.838 53.141,21.74 53.458,21.544C53.776,21.348 54.023,21.063 54.2,20.69C54.378,20.307 54.466,19.85 54.466,19.318C54.466,18.777 54.378,18.319 54.2,17.946C54.023,17.573 53.776,17.288 53.458,17.092C53.141,16.896 52.777,16.798 52.366,16.798C51.946,16.798 51.578,16.896 51.26,17.092C50.943,17.288 50.691,17.573 50.504,17.946C50.327,18.319 50.238,18.777 50.238,19.318C50.238,19.859 50.327,20.317 50.504,20.69C50.691,21.063 50.943,21.348 51.26,21.544C51.587,21.74 51.951,21.838 52.352,21.838ZM60.899,23.224C60.199,23.224 59.583,23.065 59.051,22.748C58.528,22.421 58.118,21.964 57.819,21.376C57.529,20.788 57.385,20.102 57.385,19.318C57.385,18.525 57.534,17.839 57.833,17.26C58.141,16.672 58.561,16.219 59.093,15.902C59.634,15.575 60.255,15.412 60.955,15.412C61.832,15.412 62.556,15.631 63.125,16.07C63.694,16.509 64.039,17.111 64.161,17.876L62.705,18.114C62.611,17.713 62.411,17.395 62.103,17.162C61.804,16.919 61.412,16.798 60.927,16.798C60.544,16.798 60.199,16.896 59.891,17.092C59.583,17.279 59.335,17.559 59.149,17.932C58.972,18.305 58.883,18.767 58.883,19.318C58.883,19.859 58.972,20.321 59.149,20.704C59.326,21.077 59.569,21.362 59.877,21.558C60.185,21.745 60.535,21.838 60.927,21.838C61.394,21.838 61.771,21.721 62.061,21.488C62.36,21.255 62.579,20.909 62.719,20.452L64.133,20.788C63.956,21.507 63.596,22.095 63.055,22.552C62.514,23 61.795,23.224 60.899,23.224ZM65.985,23V12.136H67.483V18.688L70.381,15.636H72.187V15.72L69.499,18.492L72.257,22.916V23H70.549L68.435,19.598L67.483,20.564V23H65.985Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_settings_button.xml b/res/drawable/bg_ps_settings_button.xml
new file mode 100644
index 0000000..c06e0c0
--- /dev/null
+++ b/res/drawable/bg_ps_settings_button.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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="@dimen/ps_button_height"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+    <path
+        android:pathData="M20,0L20,0A20,20 0,0 1,40 20L40,20A20,20 0,0 1,20 40L20,40A20,20 0,0 1,0 20L0,20A20,20 0,0 1,20 0z"
+        android:fillColor="?attr/materialColorSurfaceBright"/>
+    <group>
+        <clip-path
+            android:pathData="M10,10h20v20h-20z"/>
+        <path
+            android:pathData="M21.542,28.542H18.458C17.841,28.542 17.325,28.092 17.25,27.483L17.025,25.908C16.8,25.792 16.583,25.667 16.367,25.525L14.866,26.125C14.283,26.342 13.642,26.1 13.358,25.583L11.833,22.942C11.542,22.392 11.667,21.742 12.133,21.375L13.408,20.383C13.4,20.258 13.392,20.133 13.392,20C13.392,19.875 13.4,19.742 13.408,19.617L12.142,18.625C11.65,18.25 11.525,17.575 11.833,17.058L13.375,14.4C13.658,13.883 14.3,13.65 14.866,13.875L16.375,14.483C16.591,14.342 16.808,14.217 17.025,14.1L17.25,12.508C17.325,11.925 17.841,11.467 18.45,11.467H21.533C22.15,11.467 22.667,11.917 22.742,12.525L22.966,14.1C23.191,14.217 23.408,14.342 23.625,14.483L25.125,13.883C25.716,13.667 26.358,13.908 26.642,14.425L28.175,17.075C28.475,17.625 28.341,18.275 27.875,18.642L26.608,19.633C26.617,19.758 26.625,19.883 26.625,20.017C26.625,20.15 26.617,20.275 26.608,20.4L27.875,21.392C28.341,21.767 28.475,22.417 28.183,22.942L26.633,25.625C26.35,26.142 25.708,26.375 25.133,26.15L23.633,25.55C23.417,25.692 23.2,25.817 22.983,25.933L22.758,27.525C22.675,28.092 22.158,28.542 21.542,28.542ZM21.1,27.267C21.1,27.275 21.1,27.275 21.1,27.283V27.267ZM18.9,27.25V27.267C18.908,27.267 18.908,27.258 18.9,27.25ZM18.85,26.875H21.15L21.458,24.75L21.9,24.567C22.267,24.417 22.633,24.2 23.017,23.917L23.392,23.633L25.375,24.433L26.525,22.433L24.833,21.117L24.892,20.65C24.917,20.433 24.941,20.225 24.941,20C24.941,19.775 24.917,19.558 24.892,19.35L24.833,18.883L26.525,17.567L25.367,15.567L23.375,16.367L23,16.075C22.65,15.808 22.275,15.592 21.892,15.433L21.458,15.25L21.15,13.125H18.85L18.542,15.25L18.1,15.425C17.733,15.583 17.367,15.792 16.983,16.083L16.608,16.358L14.625,15.567L13.467,17.558L15.158,18.875L15.1,19.342C15.075,19.558 15.05,19.783 15.05,20C15.05,20.217 15.066,20.442 15.1,20.65L15.158,21.117L13.467,22.433L14.616,24.433L16.608,23.633L16.983,23.925C17.341,24.2 17.7,24.408 18.091,24.567L18.533,24.75L18.85,26.875ZM25.183,24.767C25.183,24.775 25.175,24.783 25.175,24.792L25.183,24.767ZM14.808,24.758L14.816,24.775C14.816,24.767 14.808,24.758 14.808,24.758ZM25.183,15.225C25.183,15.233 25.191,15.242 25.191,15.242L25.183,15.225ZM14.825,15.208L14.816,15.225C14.816,15.225 14.825,15.217 14.825,15.208ZM21.091,12.733C21.091,12.742 21.091,12.742 21.091,12.75V12.733ZM18.908,12.717V12.733C18.908,12.725 18.908,12.725 18.908,12.717Z"
+            android:fillColor="?attr/materialColorOnSurfaceVariant"/>
+        <path
+            android:pathData="M20,22.917C21.611,22.917 22.916,21.611 22.916,20C22.916,18.389 21.611,17.083 20,17.083C18.389,17.083 17.083,18.389 17.083,20C17.083,21.611 18.389,22.917 20,22.917Z"
+            android:fillColor="?attr/materialColorOnSurfaceVariant"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_transition_image.xml b/res/drawable/bg_ps_transition_image.xml
new file mode 100644
index 0000000..dfad3cf
--- /dev/null
+++ b/res/drawable/bg_ps_transition_image.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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:name="vector"
+    android:width="@dimen/ps_button_height"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+    <path
+        android:name="path"
+        android:pathData="M 19.998 36.668 C 10.816 36.668 3.332 29.184 3.332 20 C 3.332 10.818 10.816 3.334 19.998 3.334 C 20.916 3.334 21.666 4.084 21.666 5 C 21.666 5.918 20.916 6.668 19.998 6.668 C 12.648 6.668 6.666 12.65 6.666 20 C 6.666 27.35 12.648 33.334 19.998 33.334 C 27.348 33.334 33.332 27.35 33.332 20 C 33.332 19.084 34.082 18.334 34.998 18.334 C 35.916 18.334 36.666 19.084 36.666 20 C 36.666 29.184 29.182 36.668 19.998 36.668 Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"/>
+    <path
+        android:name="path_3"
+        android:pathData="M 20 0 C 25.302 0 30.393 2.109 34.142 5.858 C 37.891 9.607 40 14.698 40 20 C 40 25.302 37.891 30.393 34.142 34.142 C 30.393 37.891 25.302 40 20 40 C 14.698 40 9.607 37.891 5.858 34.142 C 2.109 30.393 0 25.302 0 20 C 0 14.698 2.109 9.607 5.858 5.858 C 9.607 2.109 14.698 0 20 0"
+        android:fillColor="?attr/materialColorPrimaryFixedDim"/>
+    <path
+        android:name="path_4"
+        android:pathData="M 19.999 28.334 C 15.408 28.334 11.666 24.592 11.666 20 C 11.666 15.409 15.408 11.667 19.999 11.667 C 20.458 11.667 20.833 12.042 20.833 12.5 C 20.833 12.959 20.458 13.334 19.999 13.334 C 16.324 13.334 13.333 16.325 13.333 20 C 13.333 23.675 16.324 26.667 19.999 26.667 C 23.674 26.667 26.666 23.675 26.666 20 C 26.666 19.542 27.041 19.167 27.499 19.167 C 27.958 19.167 28.333 19.542 28.333 20 C 28.333 24.592 24.591 28.334 19.999 28.334 Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_unlock_button.xml b/res/drawable/bg_ps_unlock_button.xml
new file mode 100644
index 0000000..d5eedd2
--- /dev/null
+++ b/res/drawable/bg_ps_unlock_button.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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="@dimen/ps_button_height"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="36"
+    android:viewportHeight="36">
+    <path
+        android:pathData="M18,0L18,0A18,18 0,0 1,36 18L36,18A18,18 0,0 1,18 36L18,36A18,18 0,0 1,0 18L0,18A18,18 0,0 1,18 0z"
+        android:fillColor="?attr/materialColorPrimaryFixedDim"/>
+    <path
+        android:pathData="M22.167,14.667H23C23.917,14.667 24.667,15.417 24.667,16.333V24.667C24.667,25.583 23.917,26.333 23,26.333H13C12.083,26.333 11.333,25.583 11.333,24.667V16.333C11.333,15.417 12.083,14.667 13,14.667H13.833V13C13.833,10.7 15.7,8.833 18,8.833C20.3,8.833 22.167,10.7 22.167,13V14.667ZM18,10.5C16.617,10.5 15.5,11.617 15.5,13V14.667H20.5V13C20.5,11.617 19.383,10.5 18,10.5ZM13,24.667V16.333H23V24.667H13ZM19.667,20.5C19.667,21.417 18.917,22.167 18,22.167C17.083,22.167 16.333,21.417 16.333,20.5C16.333,19.583 17.083,18.833 18,18.833C18.917,18.833 19.667,19.583 19.667,20.5Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml
new file mode 100644
index 0000000..24e290d
--- /dev/null
+++ b/res/layout/private_space_header.xml
@@ -0,0 +1,71 @@
+<?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.
+  -->
+
+<RelativeLayout
+        android:id="@+id/ps_header_layout"
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/ps_header_height"
+        android:background="@drawable/bg_ps_header"
+        android:clipToOutline="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+    <ImageButton
+        android:id="@+id/ps_lock_unlock_button"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_image_height"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:background="@android:color/transparent"
+        android:layout_marginEnd="@dimen/ps_header_layout_margin"
+        android:contentDescription="@string/ps_container_lock_unlock_button" />
+
+    <ImageButton
+        android:id="@+id/ps_settings_button"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_image_height"
+        android:layout_toStartOf="@+id/ps_lock_unlock_button"
+        android:layout_centerVertical="true"
+        android:background="@android:color/transparent"
+        android:layout_marginEnd="@dimen/ps_header_settings_icon_margin_end"
+        android:src="@drawable/bg_ps_settings_button"
+        android:contentDescription="@string/ps_container_settings" />
+
+    <ImageView
+        android:id="@+id/ps_transition_image"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_image_height"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:background="@android:color/transparent"
+        android:layout_marginEnd="@dimen/ps_header_layout_margin"
+        android:src="@drawable/bg_ps_transition_image"
+        android:contentDescription="@string/ps_container_transition" />
+
+    <TextView
+        android:id="@+id/ps_container_header"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_text_height"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:layout_marginStart="@dimen/ps_header_layout_margin"
+        android:text="@string/ps_container_title"
+        android:theme="@style/PrivateSpaceHeaderTextStyle"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c0a1e0a..ac701d6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -456,4 +456,17 @@
     <!-- Default Ime height. Used only for logging purposes.
     Assume this is default keyboard height in EN locale in case the keyboard height is not known when queried.-->
     <dimen name="default_ime_height">300dp</dimen>
+
+    <!-- Private Space parameters -->
+    <dimen name="ps_container_corner_radius">24dp</dimen>
+    <dimen name="ps_header_height">64dp</dimen>
+    <dimen name="ps_header_relative_layout_height">48dp</dimen>
+    <dimen name="ps_header_image_height">36dp</dimen>
+    <dimen name="ps_header_text_height">24dp</dimen>
+    <dimen name="ps_header_layout_margin">16dp</dimen>
+    <dimen name="ps_header_settings_icon_margin_end">8dp</dimen>
+    <dimen name="ps_header_text_size">16sp</dimen>
+    <dimen name="ps_button_height">36dp</dimen>
+    <dimen name="ps_button_width">36dp</dimen>
+    <dimen name="ps_lock_button_width">89dp</dimen>
 </resources>
diff --git a/res/values/id.xml b/res/values/id.xml
index 872ae2f..6156c91 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -45,4 +45,10 @@
 
     <item type="id" name="dismiss_view" />
 
+    <!-- Private Space parameters -->
+    <item type="id" name="ps_container_header" />
+    <item type="id" name="ps_lock_unlock_button" />
+    <item type="id" name="ps_settings_button" />
+    <item type="id" name="ps_transition_image" />
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f08f8f0..31579cd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -454,8 +454,17 @@
     <!-- Failed action error message: e.g. Failed: Pause -->
     <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
 
+    <!-- Strings for Private Space -->
     <!-- Private space label -->
     <string name="private_space_label">Private space</string>
+    <!-- Title for Private Space Container shown at the bottom of all apps drawer -->
+    <string name="ps_container_title">Private</string>
+    <!-- Description for Private Space Settings button -->
+    <string name="ps_container_settings">Private Space Settings</string>
+    <!-- Description for Private Space Lock/Unlock button -->
+    <string name="ps_container_lock_unlock_button">Lock/Unlock Private Space</string>
+    <!-- Description for Private Space Transition button -->
+    <string name="ps_container_transition">Private Space Transitioning</string>
 
     <!-- Strings for bubble bar -->
     <!-- content description for the overflow bubble [CHAR_LIMIT=none] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 82a227a..36991b1 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -435,4 +435,10 @@
         <item name="arrowTipBackground">@color/arrow_tip_view_bg</item>
         <item name="arrowTipTextColor">@color/arrow_tip_view_content</item>
     </style>
+
+    <style name="PrivateSpaceHeaderTextStyle">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">@color/material_color_on_surface</item>
+        <item name="android:fontFamily">google-sans-text-medium</item>
+    </style>
 </resources>
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 095cfa9..7d52cbb 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -563,7 +563,7 @@
             mainRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(0);
             workRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(1);
             mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
-            mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getMatcher());
+            mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getItemInfoMatcher());
             workRecyclerView.setId(R.id.apps_list_view_work);
             if (enableExpandingPauseWorkButton()
                     || FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 7baf7d3..bce38a3 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -52,13 +52,16 @@
 
     public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4;
     public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5;
-
-    public static final int NEXT_ID = 6;
+    public static final int VIEW_TYPE_PRIVATE_SPACE_HEADER = 1 << 6;
+    public static final int NEXT_ID = 7;
 
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
 
+    public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER =
+            VIEW_TYPE_PRIVATE_SPACE_HEADER;
+
     protected final SearchAdapterProvider<?> mAdapterProvider;
 
     /**
@@ -196,6 +199,9 @@
             case VIEW_TYPE_WORK_DISABLED_CARD:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.work_apps_paused, parent, false));
+            case VIEW_TYPE_PRIVATE_SPACE_HEADER:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.private_space_header, parent, false));
             default:
                 if (mAdapterProvider.isViewSupported(viewType)) {
                     return mAdapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType);
@@ -223,6 +229,7 @@
                 }
                 break;
             }
+            case VIEW_TYPE_PRIVATE_SPACE_HEADER:
             case VIEW_TYPE_ALL_APPS_DIVIDER:
             case VIEW_TYPE_WORK_DISABLED_CARD:
                 // nothing to do
diff --git a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
new file mode 100644
index 0000000..f7c9058
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.view.View;
+
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.views.ActivityContext;
+
+/**
+ * Decorator which changes the background color for Private Space Icon Rows in AllAppsContainer.
+ */
+public class PrivateAppsSectionDecorator extends RecyclerView.ItemDecoration {
+
+    private final Path mTmpPath = new Path();
+    private final RectF mTmpRect = new RectF();
+    private final Context mContext;
+    private final AlphabeticalAppsList<?> mAppsList;
+    private final PrivateProfileManager mPrivateProfileManager;
+    private final UserCache mUserCache;
+    private final Paint mPaint;
+
+    public PrivateAppsSectionDecorator(ActivityAllAppsContainerView<?> appsContainerView,
+            AlphabeticalAppsList<?> appsList,
+            PrivateProfileManager privateProfileManager) {
+        mAppsList = appsList;
+        mPrivateProfileManager = privateProfileManager;
+        mContext = appsContainerView.mActivityContext;
+        mUserCache = UserCache.getInstance(appsContainerView.mActivityContext);
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaint.setColor(ContextCompat.getColor(mContext,
+                R.color.material_color_surface_container_high));
+    }
+
+    /** Decorates Private Space Header and Icon Rows to give the shape of a container. */
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        mTmpPath.reset();
+        mTmpRect.setEmpty();
+        int numCol = ActivityContext.lookupContext(mContext).getDeviceProfile()
+                .numShownAllAppsColumns;
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            View view = parent.getChildAt(i);
+            int position = parent.getChildAdapterPosition(view);
+            BaseAllAppsAdapter.AdapterItem adapterItem = mAppsList.getAdapterItems().get(position);
+            // Rectangle that covers the bottom half of the PS Header View when Space is unlocked.
+            if (adapterItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER
+                    && mPrivateProfileManager
+                    .getCurrentState() == PrivateProfileManager.STATE_ENABLED) {
+                // We flatten the bottom corners of the rectangle, so that it merges with
+                // the private space app row decorator.
+                mTmpRect.set(
+                        view.getLeft(),
+                        view.getTop() + (float) (view.getBottom() - view.getTop()) / 2,
+                        view.getRight(),
+                        view.getBottom());
+                mTmpPath.addRect(mTmpRect, Path.Direction.CW);
+                c.drawPath(mTmpPath, mPaint);
+            } else if (adapterItem.viewType == VIEW_TYPE_ICON
+                    && mUserCache.getUserInfo(adapterItem.itemInfo.user).isPrivate()
+                    // No decoration for any private space app icon other than those at first row.
+                    && adapterItem.rowAppIndex == 0) {
+                c.drawPath(getPrivateAppRowPath(parent, view, position, numCol), mPaint);
+            }
+        }
+    }
+
+    /** Returns the path to be decorated for Private Space App Row */
+    private Path getPrivateAppRowPath(RecyclerView parent, View iconView, int adapterPosition,
+            int numCol) {
+        // We always decorate the entire app row here.
+        // As the iconView just represents the first icon of the row, we get the right margin of
+        // our decorator using the parent view.
+        mTmpRect.set(iconView.getLeft(),
+                iconView.getTop(),
+                parent.getRight() - parent.getPaddingRight(),
+                iconView.getBottom());
+        // Decorates last app row with rounded bottom corners.
+        if (adapterPosition + numCol >= mAppsList.getAdapterItems().size()) {
+            int corner = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.ps_container_corner_radius);
+            float[] mCornersBot = new float[]{0, 0, 0, 0, corner, corner, corner, corner};
+            mTmpPath.addRoundRect(mTmpRect, mCornersBot, Path.Direction.CW);
+        } else {
+            // Decorate other rows as a plain rectangle
+            mTmpPath.addRect(mTmpRect, Path.Direction.CW);
+        }
+        return mTmpPath;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
new file mode 100644
index 0000000..ec01aee
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -0,0 +1,119 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+/**
+ * Companion class for {@link ActivityAllAppsContainerView} to manage private space section related
+ * logic in the Personal tab.
+ */
+public class PrivateProfileManager extends UserProfileManager {
+
+    private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER;
+    private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key";
+    private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal";
+    private final ActivityAllAppsContainerView<?> mAllApps;
+    private final Predicate<UserHandle> mPrivateProfileMatcher;
+
+    public PrivateProfileManager(UserManager userManager,
+            UserCache userCache,
+            ActivityAllAppsContainerView allApps,
+            StatsLogManager statsLogManager) {
+        super(userManager, statsLogManager, userCache);
+        mAllApps = allApps;
+        mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate();
+    }
+
+    /** Adds Private Space Header to the layout. */
+    public int addPrivateSpaceHeader(ArrayList<BaseAllAppsAdapter.AdapterItem> adapterItems) {
+        adapterItems.add(new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER));
+        mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1);
+        return adapterItems.size();
+    }
+
+    /** Disables quiet mode for Private Space User Profile. */
+    public void unlockPrivateProfile() {
+        // TODO (b/302666597): Log this event to WW.
+        enableQuietMode(false);
+    }
+
+    /** Enables quiet mode for Private Space User Profile. */
+    public void lockPrivateProfile() {
+        // TODO (b/302666597): Log this event to WW.
+        enableQuietMode(true);
+    }
+
+    /** Whether private profile should be hidden on Launcher. */
+    public boolean isPrivateSpaceHidden() {
+        // TODO (b/289223923): Update this when we are able to read PsSettingsFlag
+        //  from SettingsProvider.
+        return false;
+    }
+
+    /** Resets the current state of Private Profile, w.r.t. to Launcher. */
+    public void reset() {
+        boolean isEnabled = !mAllApps.getAppsStore()
+                .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED);
+        int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED;
+        setCurrentState(updatedState);
+    }
+
+    /** Opens the Private Space Settings Entry Point. */
+    public void openPrivateSpaceSettings() {
+        // TODO (b/302666597): Log this event to WW.
+        Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT);
+        psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE);
+        mAllApps.getContext().startActivity(psSettingsIntent);
+    }
+
+    /**
+     * Whether Private Space Settings Entry Point should be made visible. */
+    public boolean isPrivateSpaceSettingsButtonVisible() {
+        Preconditions.assertNonUiThread();
+        Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT);
+        psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE);
+        ResolveInfo resolveInfo = mAllApps.getContext().getPackageManager()
+                .resolveActivity(psSettingsIntent, PackageManager.MATCH_SYSTEM_ONLY);
+        return resolveInfo != null;
+    }
+
+    /** Posts quiet mode enable/disable call for private profile. */
+    private void enableQuietMode(boolean enable) {
+        setQuietMode(enable);
+    }
+
+    @Override
+    public Predicate<UserHandle> getUserMatcher() {
+        return mPrivateProfileMatcher;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
new file mode 100644
index 0000000..9420b4c
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -0,0 +1,96 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED;
+import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION;
+
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
+
+/**
+ * Controller which returns views to be added to Private Space Header based upon
+ * {@link UserProfileState}
+ */
+public class PrivateSpaceHeaderViewController {
+    private final PrivateProfileManager mPrivateProfileManager;
+
+    public PrivateSpaceHeaderViewController(PrivateProfileManager privateProfileManager) {
+        this.mPrivateProfileManager = privateProfileManager;
+    }
+
+    /** Add Private Space Header view elements based upon {@link UserProfileState} */
+    public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
+        //Add quietMode image and action for lock/unlock button
+        ImageButton quietModeButton = parent.findViewById(R.id.ps_lock_unlock_button);
+        assert quietModeButton != null;
+        addQuietModeButton(quietModeButton);
+
+        //Add image and action for private space settings button
+        ImageButton settingsButton = parent.findViewById(R.id.ps_settings_button);
+        assert settingsButton != null;
+        addPrivateSpaceSettingsButton(settingsButton);
+
+        //Add image for private space transitioning view
+        ImageView transitionView = parent.findViewById(R.id.ps_transition_image);
+        assert transitionView != null;
+        addTransitionImage(transitionView);
+    }
+
+    private void addQuietModeButton(ImageButton quietModeButton) {
+        switch (mPrivateProfileManager.getCurrentState()) {
+            case STATE_ENABLED -> {
+                quietModeButton.setVisibility(View.VISIBLE);
+                quietModeButton.setImageResource(R.drawable.bg_ps_lock_button);
+                quietModeButton.setOnClickListener(
+                        view -> mPrivateProfileManager.lockPrivateProfile());
+            }
+            case STATE_DISABLED -> {
+                quietModeButton.setVisibility(View.VISIBLE);
+                quietModeButton.setImageResource(R.drawable.bg_ps_unlock_button);
+                quietModeButton.setOnClickListener(
+                        view -> mPrivateProfileManager.unlockPrivateProfile());
+            }
+            default -> quietModeButton.setVisibility(View.GONE);
+        }
+    }
+
+    private void addPrivateSpaceSettingsButton(ImageButton settingsButton) {
+        if (mPrivateProfileManager.getCurrentState() == STATE_ENABLED
+                && mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()) {
+            settingsButton.setVisibility(View.VISIBLE);
+            settingsButton.setOnClickListener(view ->
+                    mPrivateProfileManager.openPrivateSpaceSettings());
+        } else {
+            settingsButton.setVisibility(View.GONE);
+        }
+    }
+
+    private void addTransitionImage(ImageView transitionImage) {
+        if (mPrivateProfileManager.getCurrentState() == STATE_TRANSITION) {
+            transitionImage.setVisibility(View.VISIBLE);
+        } else {
+            transitionImage.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
new file mode 100644
index 0000000..0261010
--- /dev/null
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -0,0 +1,109 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Predicate;
+
+/**
+ * A Generic User Profile Manager which abstract outs the common functionality required
+ * by user-profiles supported by Launcher
+ * <p>
+ * Concrete impls are
+ * {@link WorkProfileManager} which manages work profile state
+ * {@link PrivateProfileManager} which manages private profile state.
+ */
+public abstract class UserProfileManager {
+    public static final int STATE_ENABLED = 1;
+    public static final int STATE_DISABLED = 2;
+    public static final int STATE_TRANSITION = 3;
+
+    @IntDef(value = {
+            STATE_ENABLED,
+            STATE_DISABLED,
+            STATE_TRANSITION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserProfileState { }
+
+    @UserProfileState
+    private int mCurrentState;
+
+    private final UserManager mUserManager;
+    private final StatsLogManager mStatsLogManager;
+    private final UserCache mUserCache;
+
+    protected UserProfileManager(UserManager userManager,
+            StatsLogManager statsLogManager,
+            UserCache userCache) {
+        mUserManager = userManager;
+        mStatsLogManager = statsLogManager;
+        mUserCache = userCache;
+    }
+
+    /** Sets quiet mode as enabled/disabled for the profile type. */
+    protected void setQuietMode(boolean enabled) {
+        if (Utilities.ATLEAST_P) {
+            UI_HELPER_EXECUTOR.post(() -> {
+                mUserCache.getUserProfiles()
+                        .stream()
+                        .filter(getUserMatcher())
+                        .findFirst()
+                        .ifPresent(userHandle ->
+                                mUserManager.requestQuietModeEnabled(enabled, userHandle));
+            });
+        }
+    }
+
+    /** Sets current state for the profile type. */
+    protected void setCurrentState(int state) {
+        mCurrentState = state;
+    }
+
+    /** Returns current state for the profile type. */
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    public int getCurrentState() {
+        return mCurrentState;
+    }
+
+    /** Logs Event to StatsLogManager. */
+    protected void logEvents(StatsLogManager.EventEnum event) {
+        mStatsLogManager.logger().log(event);
+    }
+
+    /** Returns the matcher corresponding to profile type. */
+    protected abstract Predicate<UserHandle> getUserMatcher();
+
+    /** Returns the matcher corresponding to the profile type associated with ItemInfo. */
+    protected Predicate<ItemInfo> getItemInfoMatcher() {
+        return itemInfo -> itemInfo != null && getUserMatcher().test(itemInfo.user);
+    }
+}
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 61c3d3f..c430a36 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -26,18 +26,14 @@
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
-import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.Flags;
@@ -46,12 +42,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -59,62 +52,29 @@
 /**
  * Companion class for {@link ActivityAllAppsContainerView} to manage work tab and personal tab
  * related
- * logic based on {@link WorkProfileState}?
+ * logic based on {@link UserProfileState}?
  */
-public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
+public class WorkProfileManager extends UserProfileManager
+        implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
     private static final String TAG = "WorkProfileManager";
-
-    public static final int STATE_ENABLED = 1;
-    public static final int STATE_DISABLED = 2;
-    public static final int STATE_TRANSITION = 3;
-
-    /**
-     * Work profile manager states
-     */
-    @IntDef(value = {
-            STATE_ENABLED,
-            STATE_DISABLED,
-            STATE_TRANSITION
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface WorkProfileState { }
-
-    private final UserManager mUserManager;
     private final ActivityAllAppsContainerView<?> mAllApps;
-    private final Predicate<ItemInfo> mMatcher;
-    private final StatsLogManager mStatsLogManager;
-
     private WorkModeSwitch mWorkModeSwitch;
-
-    private final UserCache mUserCache;
-
-    @WorkProfileState
-    private int mCurrentState;
+    private final Predicate<UserHandle> mWorkProfileMatcher;
 
     public WorkProfileManager(
             UserManager userManager, ActivityAllAppsContainerView allApps,
             StatsLogManager statsLogManager, UserCache userCache) {
-        mUserManager = userManager;
+        super(userManager, statsLogManager, userCache);
         mAllApps = allApps;
-        mStatsLogManager = statsLogManager;
-        mUserCache = userCache;
-        mMatcher = info -> info != null && mUserCache.getUserInfo(info.user).isWork();
+        mWorkProfileMatcher = (user) -> userCache.getUserInfo(user).isWork();
     }
 
     /**
      * Posts quite mode enable/disable call for work profile user
      */
-    @RequiresApi(Build.VERSION_CODES.P)
     public void setWorkProfileEnabled(boolean enabled) {
-        updateCurrentState(STATE_TRANSITION);
-        UI_HELPER_EXECUTOR.post(() -> {
-            for (UserHandle userProfile : mUserCache.getUserProfiles()) {
-                if (mUserCache.getUserInfo(userProfile).isWork()) {
-                    mUserManager.requestQuietModeEnabled(!enabled, userProfile);
-                    break;
-                }
-            }
-        });
+        setCurrentState(STATE_TRANSITION);
+        setQuietMode(!enabled);
     }
 
     @Override
@@ -126,7 +86,7 @@
         if (mWorkModeSwitch != null) {
             if (page == MAIN || page == SEARCH) {
                 mWorkModeSwitch.animateVisibility(false);
-            } else if (page == WORK && mCurrentState == STATE_ENABLED) {
+            } else if (page == WORK && getCurrentState() == STATE_ENABLED) {
                 mWorkModeSwitch.animateVisibility(true);
             }
         }
@@ -151,17 +111,17 @@
         }
     }
 
-    private void updateCurrentState(@WorkProfileState int currentState) {
-        mCurrentState = currentState;
+    private void updateCurrentState(@UserProfileState int currentState) {
+        setCurrentState(currentState);
         if (getAH() != null) {
             getAH().mAppsList.updateAdapterItems();
         }
         if (mWorkModeSwitch != null) {
             updateWorkFAB(mAllApps.getCurrentPage());
         }
-        if (mCurrentState == STATE_ENABLED) {
+        if (getCurrentState() == STATE_ENABLED) {
             attachWorkModeSwitch();
-        } else if (mCurrentState == STATE_DISABLED) {
+        } else if (getCurrentState() == STATE_DISABLED) {
             detachWorkModeSwitch();
         }
     }
@@ -201,10 +161,6 @@
         mWorkModeSwitch = null;
     }
 
-    public Predicate<ItemInfo> getMatcher() {
-        return mMatcher;
-    }
-
     @Nullable
     public WorkModeSwitch getWorkModeSwitch() {
         return mWorkModeSwitch;
@@ -214,29 +170,25 @@
         return mAllApps.mAH.get(WORK);
     }
 
-    public int getCurrentState() {
-        return mCurrentState;
-    }
-
     /**
      * returns whether or not work apps should be visible in work tab.
      */
     public boolean shouldShowWorkApps() {
-        return mCurrentState != WorkProfileManager.STATE_DISABLED;
+        return getCurrentState() != WorkProfileManager.STATE_DISABLED;
     }
 
     public boolean hasWorkApps() {
-        return Stream.of(mAllApps.getAppsStore().getApps()).anyMatch(mMatcher);
+        return Stream.of(mAllApps.getAppsStore().getApps()).anyMatch(getItemInfoMatcher());
     }
 
     /**
      * Adds work profile specific adapter items to adapterItems and returns number of items added
      */
     public int addWorkItems(ArrayList<AdapterItem> adapterItems) {
-        if (mCurrentState == WorkProfileManager.STATE_DISABLED) {
+        if (getCurrentState() == WorkProfileManager.STATE_DISABLED) {
             //add disabled card here.
             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
-        } else if (mCurrentState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
+        } else if (getCurrentState() == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
         }
         return adapterItems.size();
@@ -247,8 +199,9 @@
     }
 
     private void onWorkFabClicked(View view) {
-        if (Utilities.ATLEAST_P && mCurrentState == STATE_ENABLED && mWorkModeSwitch.isEnabled()) {
-            mStatsLogManager.logger().log(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
+        if (Utilities.ATLEAST_P && getCurrentState() == STATE_ENABLED
+                && mWorkModeSwitch.isEnabled()) {
+            logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
             setWorkProfileEnabled(false);
         }
     }
@@ -279,4 +232,9 @@
             }
         };
     }
+
+    @Override
+    public Predicate<UserHandle> getUserMatcher() {
+        return mWorkProfileMatcher;
+    }
 }
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
new file mode 100644
index 0000000..bfa9241
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.UserIconInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class PrivateProfileManagerTest {
+
+    private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+    private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
+    private static final UserIconInfo MAIN_ICON_INFO =
+            new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN);
+    private static final UserIconInfo PRIVATE_ICON_INFO =
+            new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE);
+    private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER;
+    private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key";
+    private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal";
+
+    private PrivateProfileManager mPrivateProfileManager;
+    @Mock
+    private ActivityAllAppsContainerView mActivityAllAppsContainerView;
+    @Mock
+    private StatsLogManager mStatsLogManager;
+    @Mock
+    private UserCache mUserCache;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private Context mContext;
+    @Mock
+    private AllAppsStore mAllAppsStore;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mUserCache.getUserProfiles())
+                .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
+        when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO);
+        when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
+        when(mActivityAllAppsContainerView.getContext()).thenReturn(mContext);
+        when(mActivityAllAppsContainerView.getAppsStore()).thenReturn(mAllAppsStore);
+        mPrivateProfileManager = new PrivateProfileManager(mUserManager, mUserCache,
+                mActivityAllAppsContainerView, mStatsLogManager);
+    }
+
+    @Test
+    public void lockPrivateProfile_requestsQuietModeAsTrue() throws Exception {
+        when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(false);
+
+        mPrivateProfileManager.lockPrivateProfile();
+
+        awaitTasksCompleted();
+        Mockito.verify(mUserManager).requestQuietModeEnabled(true, PRIVATE_HANDLE);
+    }
+
+    @Test
+    public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception {
+        when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true);
+
+        mPrivateProfileManager.unlockPrivateProfile();
+
+        awaitTasksCompleted();
+        Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE);
+    }
+
+    @Test
+    public void quietModeFlagPresent_privateSpaceIsResetToDisabled() {
+        when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
+                .thenReturn(false, true);
+
+        // In first call the state should be disabled.
+        mPrivateProfileManager.reset();
+        assertEquals(STATE_ENABLED, mPrivateProfileManager.getCurrentState());
+
+        // In the next call the state should be disabled.
+        mPrivateProfileManager.reset();
+        assertEquals(STATE_DISABLED, mPrivateProfileManager.getCurrentState());
+    }
+
+    @Test
+    public void openPrivateSpaceSettings_triggersSecurityAndPrivacyIntent() {
+        Intent expectedIntent = new Intent(SAFETY_CENTER_INTENT);
+        expectedIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE);
+        ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
+
+        mPrivateProfileManager.openPrivateSpaceSettings();
+
+        Mockito.verify(mContext).startActivity(acIntent.capture());
+        Intent actualIntent = acIntent.getValue();
+        assertEquals(expectedIntent.getAction(), actualIntent.getAction());
+        assertEquals(expectedIntent.getStringExtra(PS_SETTINGS_FRAGMENT_KEY),
+                actualIntent.getStringExtra(PS_SETTINGS_FRAGMENT_KEY));
+    }
+
+    private static void awaitTasksCompleted() throws Exception {
+        UI_HELPER_EXECUTOR.submit(() -> null).get();
+    }
+}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
new file mode 100644
index 0000000..87adaa1
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.allapps;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ActivityContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PrivateSpaceHeaderViewControllerTest {
+
+    private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1;
+    private static final int LOCK_UNLOCK_BUTTON_COUNT = 1;
+    private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1;
+    private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0;
+    private static final int PS_TRANSITION_IMAGE_COUNT = 1;
+
+    private Context mContext;
+    private LayoutInflater mLayoutInflater;
+    private PrivateSpaceHeaderViewController mPsHeaderViewController;
+    private RelativeLayout mPsHeaderLayout;
+    @Mock
+    private PrivateProfileManager mPrivateProfileManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = new ActivityContextWrapper(getApplicationContext());
+        mLayoutInflater = LayoutInflater.from(getApplicationContext());
+        mPsHeaderViewController = new PrivateSpaceHeaderViewController(mPrivateProfileManager);
+        mPsHeaderLayout = (RelativeLayout) mLayoutInflater.inflate(R.layout.private_space_header,
+                null);
+    }
+
+    @Test
+    public void privateProfileDisabled_psHeaderContainsLockedView() {
+        Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.bg_ps_unlock_button));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_lock_unlock_button
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(unlockButton);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+    }
+
+    @Test
+    public void privateProfileEnabled_psHeaderContainsUnlockedView() {
+        Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button));
+        Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_settings_button));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()).thenReturn(true);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        int totalSettingsImageView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_lock_unlock_button
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(lockImage);
+            } else if (view.getId() == R.id.ps_settings_button
+                    && view instanceof ImageView imageView) {
+                totalSettingsImageView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(settingsImage);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+        assertEquals(PS_SETTINGS_BUTTON_COUNT_VISIBLE, totalSettingsImageView);
+    }
+
+    @Test
+    public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView() {
+        Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()).thenReturn(false);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        int totalSettingsImageView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_lock_unlock_button
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(lockImage);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+        assertEquals(PS_SETTINGS_BUTTON_COUNT_INVISIBLE, totalSettingsImageView);
+    }
+
+    @Test
+    public void privateProfileTransitioning_psHeaderContainsTransitionView() {
+        Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_transition_image
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(transitionImage);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView);
+    }
+
+    private Bitmap getBitmap(Drawable drawable) {
+        Bitmap result;
+        if (drawable instanceof BitmapDrawable) {
+            result = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            int width = drawable.getIntrinsicWidth();
+            int height = drawable.getIntrinsicHeight();
+            // Some drawables have no intrinsic width - e.g. solid colours.
+            if (width <= 0) {
+                width = 1;
+            }
+            if (height <= 0) {
+                height = 1;
+            }
+
+            result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(result);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+        }
+        return result;
+    }
+}