Merge changes from topic "state-supplier" into ub-launcher3-master

* changes:
  Translate recents slightly while dragging after pausing
  Two-zone model: swipe up from nav bar vs above it
  Change LauncherState to Supplier<LauncherState> in tests
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
index a66b929..5751ed9 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
@@ -17,9 +17,13 @@
 
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 
 /**
  * Interface representing a bitmap draw operation.
@@ -29,6 +33,7 @@
     boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
 
     static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
+        GraphicsUtils.noteNewBitmapCreated();
         Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         renderer.draw(new Canvas(result));
         return result;
@@ -40,11 +45,26 @@
             return createSoftwareBitmap(width, height, renderer);
         }
 
+        GraphicsUtils.noteNewBitmapCreated();
         Picture picture = new Picture();
         renderer.draw(picture.beginRecording(width, height));
         picture.endRecording();
         return Bitmap.createBitmap(picture);
     }
 
+    /**
+     * Returns a bitmap from subset of the source bitmap. The new bitmap may be the
+     * same object as source, or a copy may have been made.
+     */
+    static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
+        if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) {
+            return createHardwareBitmap(width, height, c -> c.drawBitmap(source,
+                    new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null));
+        } else {
+            GraphicsUtils.noteNewBitmapCreated();
+            return Bitmap.createBitmap(source, x, y, width, height);
+        }
+    }
+
     void draw(Canvas out);
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
index 3e818a5..97eef66 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -21,11 +21,11 @@
 import android.graphics.RegionIterator;
 import android.util.Log;
 
+import androidx.annotation.ColorInt;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
-import androidx.annotation.ColorInt;
-
 public class GraphicsUtils {
 
     private static final String TAG = "GraphicsUtils";
@@ -73,4 +73,9 @@
         }
         return area;
     }
+
+    /**
+     * Utility method to track new bitmap creation
+     */
+    public static void noteNewBitmapCreated() { }
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
index 5df8043..7702727 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.BlurMaskFilter;
 import android.graphics.BlurMaskFilter.Blur;
 import android.graphics.Canvas;
@@ -135,9 +134,7 @@
             bounds.offsetTo(center - width / 2f, center - height / 2f);
 
             int size = center * 2;
-            Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
-            drawShadow(new Canvas(result));
-            return result;
+            return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow);
         }
 
         public void drawShadow(Canvas c) {
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
index f0e70a8..fa3a0f8 100644
--- a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -15,5 +15,5 @@
 -->
 <vector android:height="24dp" android:viewportHeight="24"
     android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="@color/hotseat_edu_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
+    <path android:fillColor="@color/bottom_panel_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
 </vector>
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
index ee38e3b..a7cd167 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -24,12 +24,13 @@
     <View
         android:layout_width="match_parent"
         android:layout_height="32dp"
-        android:background="@drawable/hotseat_prediction_edu_top" />
+        android:backgroundTint="@color/bottom_panel_background"
+        android:background="@drawable/bottom_sheet_top_border" />
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/hotseat_edu_background"
+        android:background="@color/bottom_panel_background"
         android:orientation="vertical">
 
         <TextView
@@ -37,8 +38,8 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="18dp"
             android:fontFamily="google-sans"
-            android:paddingLeft="@dimen/hotseat_edu_padding"
-            android:paddingRight="@dimen/hotseat_edu_padding"
+            android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+            android:paddingRight="@dimen/bottom_sheet_edu_padding"
             android:text="@string/hotseat_migrate_title"
             android:textAlignment="center"
             android:textColor="@android:color/white"
@@ -50,8 +51,8 @@
             android:layout_marginTop="18dp"
             android:layout_marginBottom="18dp"
             android:fontFamily="roboto-medium"
-            android:paddingLeft="@dimen/hotseat_edu_padding"
-            android:paddingRight="@dimen/hotseat_edu_padding"
+            android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+            android:paddingRight="@dimen/bottom_sheet_edu_padding"
             android:text="@string/hotseat_migrate_message"
             android:textAlignment="center"
             android:textColor="@android:color/white"
@@ -72,9 +73,9 @@
             <FrameLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:paddingLeft="@dimen/hotseat_edu_padding"
+                android:paddingLeft="@dimen/bottom_sheet_edu_padding"
                 android:paddingTop="8dp"
-                android:paddingRight="@dimen/hotseat_edu_padding">
+                android:paddingRight="@dimen/bottom_sheet_edu_padding">
 
                 <Button
                     android:id="@+id/turn_predictions_on"
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 4fa5684..7426e30 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -6,6 +6,4 @@
     <color name="all_apps_label_text_dark">#61FFFFFF</color>
     <color name="all_apps_prediction_row_separator">#3c000000</color>
     <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
-
-    <color name="hotseat_edu_background">#f01A73E8</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index c458ec7..de97d08 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -29,7 +29,4 @@
     <dimen name="swipe_up_y_overshoot">10dp</dimen>
     <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
 
-    <!-- Hybrid hotseat related -->
-    <dimen name="hotseat_edu_padding">24dp</dimen>
-
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 923e050..bfbd00e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -109,7 +109,7 @@
                 NOTIFICATION_CHANNEL_ID)
                 .setContentTitle(name)
                 .setOngoing(true)
-                .setColor(mLauncher.getColor(R.color.hotseat_edu_background))
+                .setColor(mLauncher.getColor(R.color.bottom_panel_background))
                 .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
                         PendingIntent.FLAG_CANCEL_CURRENT))
                 .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index cb20ed0..c1ea533 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -1918,6 +1918,11 @@
 
     @Override
     public void addView(View child, int index) {
+        // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
+        // child direction back to match system settings.
+        child.setLayoutDirection(
+                Utilities.isRtl(getResources())
+                        ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         super.addView(child, index);
         if (isExtraCardView(child, index)) {
             mTaskViewStartIndex++;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 5539b3e..3c6537a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -18,6 +18,8 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -25,6 +27,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.MotionEvent;
+
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.quickstep.util.SharedApiCompat;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -282,4 +285,15 @@
             }
         }
     }
+
+    @Override
+    public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.handleImageAsScreenshot(bitmap, rect, insets, i);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call handleImageAsScreenshot", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index a466f12..53859ad 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
-import com.android.systemui.shared.system.StatsLogCompat;
+import com.android.systemui.shared.system.SysUiStatsLog;
 
 import com.google.protobuf.nano.MessageNano;
 
@@ -72,8 +72,8 @@
         if (ext.srcTarget[0] != null) {
             ext.srcTarget[0].item = LauncherTarget.APP_ICON;
         }
-        StatsLogCompat.write(LAUNCH_APP, srcState, BACKGROUND /* dstState */,
-                MessageNano.toByteArray(ext), true);
+        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_APP, srcState,
+                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
     }
 
     @Override
@@ -82,8 +82,8 @@
         ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
         int srcState = OVERVIEW;
         fillInLauncherExtension(v, ext);
-        StatsLogCompat.write(LAUNCH_TASK, srcState, BACKGROUND /* dstState */,
-                MessageNano.toByteArray(ext), true);
+        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_TASK, srcState,
+                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
     }
 
     @Override
@@ -92,8 +92,8 @@
         ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
         int srcState = OVERVIEW;
         fillInLauncherExtension(v, ext);
-        StatsLogCompat.write(DISMISS_TASK, srcState, BACKGROUND /* dstState */,
-                MessageNano.toByteArray(ext), true);
+        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, DISMISS_TASK, srcState,
+                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
     }
 
     @Override
@@ -103,7 +103,7 @@
         int srcState = mStateProvider.getCurrentState();
         fillInLauncherExtensionWithPageId(ext, pageId);
         int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
-        StatsLogCompat.write(launcherAction, srcState, srcState,
+        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, launcherAction, srcState, srcState,
                 MessageNano.toByteArray(ext), true);
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 2804c0e..c99df10 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -56,6 +56,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mLauncherPid = 0;
         super.setUp();
         TaplTestsLauncher3.initialize(this);
         mLauncherPid = mLauncher.getPid();
@@ -63,7 +64,9 @@
 
     @After
     public void teardown() {
-        assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+        if (mLauncherPid != 0) {
+            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+        }
     }
 
     private void startTestApps() throws Exception {
diff --git a/res/drawable-hdpi/work_tab_user_education.png b/res/drawable-hdpi/work_tab_user_education.png
deleted file mode 100644
index 1879dfb..0000000
--- a/res/drawable-hdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/work_tab_user_education.png b/res/drawable-mdpi/work_tab_user_education.png
deleted file mode 100644
index 65c7e63..0000000
--- a/res/drawable-mdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/work_tab_user_education.png b/res/drawable-xhdpi/work_tab_user_education.png
deleted file mode 100644
index 59df7a8..0000000
--- a/res/drawable-xhdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/work_tab_user_education.png b/res/drawable-xxhdpi/work_tab_user_education.png
deleted file mode 100644
index 3c6aa20..0000000
--- a/res/drawable-xxhdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml b/res/drawable/bottom_sheet_top_border.xml
similarity index 84%
rename from quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
rename to res/drawable/bottom_sheet_top_border.xml
index e3cc549..23f4e51 100644
--- a/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
+++ b/res/drawable/bottom_sheet_top_border.xml
@@ -15,5 +15,5 @@
 -->
 <vector android:height="15.53398dp" android:viewportHeight="32"
     android:viewportWidth="412" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="@color/hotseat_edu_background" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
 </vector>
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index a1033f0..893d796 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -41,7 +41,7 @@
         android:paddingLeft="12dp"
         android:paddingRight="12dp" >
 
-        <com.android.launcher3.ExtendedEditText
+        <com.android.launcher3.folder.FolderNameEditText
             android:id="@+id/folder_name"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
new file mode 100644
index 0000000..a8e3d20
--- /dev/null
+++ b/res/layout/work_profile_edu.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 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.views.WorkEduView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom"
+    android:gravity="bottom"
+    android:orientation="vertical">
+
+    <View
+        android:layout_width="match_parent"
+        android:backgroundTint="@color/bottom_panel_background"
+        android:layout_height="32dp"
+        android:background="@drawable/bottom_sheet_top_border" />
+
+    <LinearLayout
+        android:id="@+id/view_wrapper"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/bottom_panel_background"
+        android:orientation="vertical"
+        android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+        android:paddingRight="@dimen/bottom_sheet_edu_padding">
+
+        <TextView
+            android:id="@+id/content_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="48dp"
+            android:layout_marginBottom="48dp"
+            android:fontFamily="google-sans"
+            android:text="@string/work_profile_edu_personal_apps"
+            android:textAlignment="center"
+            android:textColor="@android:color/white"
+            android:textSize="20sp" />
+
+        <Button
+            android:id="@+id/proceed"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:layout_gravity="end"
+            android:background="?android:attr/selectableItemBackground"
+            android:gravity="center"
+            android:text="@string/work_profile_edu_next"
+            android:textAlignment="center"
+            android:textColor="@android:color/white" />
+    </LinearLayout>
+
+</com.android.launcher3.views.WorkEduView>
\ No newline at end of file
diff --git a/res/layout/work_tab_bottom_user_education_view.xml b/res/layout/work_tab_bottom_user_education_view.xml
deleted file mode 100644
index ac2deeb..0000000
--- a/res/layout/work_tab_bottom_user_education_view.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.views.BottomUserEducationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="bottom"
-    android:background="?android:attr/colorAccent"
-    android:elevation="2dp"
-    android:focusable="true"
-    android:orientation="horizontal">
-
-  <ImageView
-      android:layout_width="134dp"
-      android:layout_height="134dp"
-      android:layout_marginTop="28dp"
-      android:layout_marginLeft="20dp"
-      android:src="@drawable/work_tab_user_education"/>
-
-  <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:layout_marginStart="24dp"
-      android:orientation="vertical">
-
-    <ImageView
-        android:id="@+id/close_bottom_user_tip"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginTop="12dp"
-        android:layout_marginEnd="12dp"
-        android:layout_gravity="right"
-        android:contentDescription="@string/bottom_work_tab_user_education_close_button"
-        android:src="@drawable/ic_remove_no_shadow"/>
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="4dp"
-        android:layout_marginEnd="24dp"
-        android:fontFamily="roboto-medium"
-        android:text="@string/bottom_work_tab_user_education_title"
-        android:textColor="@android:color/white"
-        android:textSize="20sp"/>
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="24dp"
-        android:text="@string/bottom_work_tab_user_education_body"
-        android:textColor="@android:color/white"
-        android:textSize="14sp"/>
-
-  </LinearLayout>
-
-</com.android.launcher3.views.BottomUserEducationView>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 815ae21..36f8468 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -43,4 +43,7 @@
     <color name="back_gesture_tutorial_title_color">#FF000000</color>
     <color name="back_gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
     <color name="back_gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
+
+
+    <color name="bottom_panel_background">#f01A73E8</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4bcb8a7..0293573 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -235,4 +235,7 @@
 <!-- Theming related -->
     <dimen name="default_dialog_corner_radius">8dp</dimen>
 
+    <!-- Onboarding bottomsheet related -->
+    <dimen name="bottom_sheet_edu_padding">24dp</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 218f6db..3f279f4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -327,17 +327,20 @@
 
     <!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
     <string name="work_profile_toggle_label">Work profile</string>
-    <!-- Title of an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer. "Work apps" are apps in a user's work profile.-->
-    <string name="bottom_work_tab_user_education_title">Find work apps here</string>
-    <!-- Text in an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer.-->
-    <string name="bottom_work_tab_user_education_body">Each work app has a badge and is kept secure by your organization. Move apps to your Home screen for easier access.</string>
+    <!--- User onboarding title for personal apps -->
+    <string name="work_profile_edu_personal_apps">Personal apps are private &amp; can\'t be seen by IT</string>
+    <!--- User onboarding title for work profile apps -->
+    <string name="work_profile_edu_work_apps">Work apps are badged and monitored by IT</string>
+    <!-- Action label to proceed to the next work profile edu section-->
+    <string name="work_profile_edu_next">Next</string>
+    <!-- Action label to finish work profile edu-->
+    <string name="work_profile_edu_accept">Got it</string>
+
     <!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
     "Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
     <string name="work_mode_on_label">Managed by your organization</string>
     <!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
     <string name="work_mode_off_label">Notifications and apps are off</string>
-    <string name="bottom_work_tab_user_education_close_button">Close</string>
-    <string name="bottom_work_tab_user_education_closed">Closed</string>
 
     <!-- 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>
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 86a6e8c..6059981 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -29,7 +29,7 @@
     mockito-robolectric-prebuilt \
     truth-prebuilt
 LOCAL_JAVA_LIBRARIES := \
-    platform-robolectric-4.3-prebuilt
+    platform-robolectric-4.3.1-prebuilt
 
 LOCAL_JAVA_RESOURCE_DIRS := resources config
 
@@ -56,4 +56,4 @@
 
 LOCAL_ROBOTEST_TIMEOUT := 36000
 
-include prebuilts/misc/common/robolectric/4.3/run_robotests.mk
+include prebuilts/misc/common/robolectric/4.3.1/run_robotests.mk
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index 932b01b..3d78689 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -1 +1 @@
-sdk=28
+sdk=29
diff --git a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
index 6223760..7072adf 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
@@ -16,14 +16,32 @@
 
 package com.android.launcher3.model;
 
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+
 import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
+import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
+import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.robolectric.util.ReflectionHelpers.setField;
 
+import android.app.backup.BackupManager;
+import android.content.pm.PackageInstaller;
+import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.UserHandle;
+import android.os.UserManager;
 
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.shadows.LShadowBackupManager;
+import com.android.launcher3.shadows.LShadowUserManager;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.util.LauncherRoboTestRunner;
 
@@ -32,6 +50,7 @@
 import org.junit.runner.RunWith;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.LooperMode;
+import org.robolectric.shadow.api.Shadow;
 
 /**
  * Tests to verify backup and restore flow.
@@ -40,18 +59,123 @@
 @LooperMode(LooperMode.Mode.PAUSED)
 public class BackupRestoreTest {
 
+    private static final long MY_OLD_PROFILE_ID = 1;
+    private static final long MY_PROFILE_ID = 0;
+    private static final long OLD_WORK_PROFILE_ID = 11;
+    private static final int WORK_PROFILE_ID = 10;
+
+    private static final int SYSTEM_USER = 0;
+    private static final int FLAG_SYSTEM = 0x00000800;
+    private static final int FLAG_PROFILE = 0x00001000;
+
+    private LShadowUserManager mUserManager;
+    private BackupManager mBackupManager;
     private LauncherModelHelper mModelHelper;
     private SQLiteDatabase mDb;
+    private InvariantDeviceProfile mIdp;
+    private UserHandle mMainProfileUser;
+    private UserHandle mWorkProfileUser;
 
     @Before
     public void setUp() {
+        setupUserManager();
+        setupBackupManager();
         mModelHelper = new LauncherModelHelper();
         RestoreDbTask.setPending(RuntimeEnvironment.application, true);
         mDb = mModelHelper.provider.getDb();
+        mIdp = InvariantDeviceProfile.INSTANCE.get(RuntimeEnvironment.application);
+    }
+
+    private void setupUserManager() {
+        final UserManager userManager = RuntimeEnvironment.application.getSystemService(
+                UserManager.class);
+        mUserManager = Shadow.extract(userManager);
+        // sign in to primary user
+        mMainProfileUser = mUserManager.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
+        // sign in to work profile
+        mWorkProfileUser = mUserManager.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE);
+    }
+
+    private void setupBackupManager() {
+        mBackupManager = new BackupManager(RuntimeEnvironment.application);
+        final LShadowBackupManager bm = Shadow.extract(mBackupManager);
+        bm.addProfile(MY_OLD_PROFILE_ID, mMainProfileUser);
+        bm.addProfile(OLD_WORK_PROFILE_ID, mWorkProfileUser);
     }
 
     @Test
     public void testOnCreateDbIfNotExists_CreatesBackup() {
         assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
     }
+
+    @Test
+    public void testOnRestoreSessionWithValidCondition_PerformsRestore() throws Exception {
+        setupBackup();
+        verifyTableIsFilled(BACKUP_TABLE_NAME, false);
+        verifyTableIsEmpty(TABLE_NAME);
+        createRestoreSession();
+        verifyTableIsFilled(TABLE_NAME, true);
+    }
+
+    private void setupBackup() {
+        createTableUsingOldProfileId();
+        // setup grid for main user on first screen
+        mModelHelper.createGrid(new int[][][]{{
+                { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
+                { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+                { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+                { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
+            }}, 1, MY_OLD_PROFILE_ID);
+        // setup grid for work profile on second screen
+        mModelHelper.createGrid(new int[][][]{{
+                { NO__ICON, APP_ICON, SHORTCUT, SHORTCUT},
+                { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+                { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+                { APP_ICON, SHORTCUT, SHORTCUT, NO__ICON},
+            }}, 2, OLD_WORK_PROFILE_ID);
+        // simulates the creation of backup upon restore
+        new GridBackupTable(RuntimeEnvironment.application, mDb, mIdp.numHotseatIcons,
+                mIdp.numColumns, mIdp.numRows).doBackup(
+                        MY_OLD_PROFILE_ID, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
+        // reset favorites table
+        createTableUsingOldProfileId();
+    }
+
+    private void verifyTableIsEmpty(String tableName) {
+        assertEquals(0, getCount(mDb, "SELECT * FROM " + tableName));
+    }
+
+    private void verifyTableIsFilled(String tableName, boolean sanitized) {
+        assertEquals(sanitized ? 12 : 13, getCount(mDb,
+                "SELECT * FROM " + tableName + " WHERE profileId = "
+                        + (sanitized ? MY_PROFILE_ID : MY_OLD_PROFILE_ID)));
+        assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = "
+                + (sanitized ? WORK_PROFILE_ID : OLD_WORK_PROFILE_ID)));
+    }
+
+    private void createTableUsingOldProfileId() {
+        // simulates the creation of favorites table on old device
+        dropTable(mDb, TABLE_NAME);
+        addTableToDb(mDb, MY_OLD_PROFILE_ID, false);
+    }
+
+    private void createRestoreSession() throws Exception {
+        final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        final PackageInstaller installer = RuntimeEnvironment.application.getPackageManager()
+                .getPackageInstaller();
+        final int sessionId = installer.createSession(params);
+        final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
+        setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE);
+        // TODO: (b/148410677) we should verify the following call instead
+        //  InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info);
+        RestoreDbTask.restoreIfPossible(RuntimeEnvironment.application,
+                mModelHelper.provider.getHelper(), mBackupManager);
+    }
+
+    private static int getCount(SQLiteDatabase db, String sql) {
+        try (Cursor c = db.rawQuery(sql, null)) {
+            return c.getCount();
+        }
+    }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
new file mode 100644
index 0000000..eae0101
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.shadows;
+
+import android.app.backup.BackupManager;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+
+import androidx.annotation.Nullable;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBackupManager;
+
+/**
+ * Extension of {@link ShadowBackupManager} with missing shadow methods
+ */
+@Implements(value = BackupManager.class)
+public class LShadowBackupManager extends ShadowBackupManager {
+
+    private LongSparseArray<UserHandle> mProfileMapping = new LongSparseArray<>();
+
+    public void addProfile(long userSerial, UserHandle userHandle) {
+        mProfileMapping.put(userSerial, userHandle);
+    }
+
+    @Implementation
+    @Nullable
+    public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
+        return mProfileMapping.get(ancestralSerialNumber);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
index 166e28b..f16ed33 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -26,6 +26,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -43,6 +44,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 /**
@@ -111,6 +113,17 @@
         return true;
     }
 
+    @Implementation
+    public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
+        return RuntimeEnvironment.application.getPackageManager().getPackageInstaller()
+                .getAllSessions();
+    }
+
+    @Implementation
+    public void registerPackageInstallerSessionCallback(
+            Executor executor, PackageInstaller.SessionCallback callback) {
+    }
+
     @Override
     protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
             UserHandle user) {
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index e8b7157..e133cf2 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import static org.mockito.Mockito.atLeast;
@@ -30,6 +31,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
+import android.os.Process;
 import android.provider.Settings;
 
 import com.android.launcher3.AppInfo;
@@ -42,6 +44,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.pm.UserCache;
 
 import org.mockito.ArgumentCaptor;
 import org.robolectric.Robolectric;
@@ -80,15 +83,17 @@
     private static final int DEFAULT_BITMAP_SIZE = 10;
     private static final int DEFAULT_GRID_SIZE = 4;
 
-
     private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
     public final TestLauncherProvider provider;
+    private final long mDefaultProfileId;
 
     private BgDataModel mDataModel;
     private AllAppsList mAllAppsList;
 
     public LauncherModelHelper() {
         provider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+        mDefaultProfileId = UserCache.INSTANCE.get(RuntimeEnvironment.application)
+                .getSerialNumberForUser(Process.myUserHandle());
         ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider);
     }
 
@@ -224,12 +229,16 @@
         return item;
     }
 
+    public int addItem(int type, int screen, int container, int x, int y) {
+        return addItem(type, screen, container, x, y, mDefaultProfileId);
+    }
+
     /**
      * Adds a dummy item in the DB.
      * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
      *             folder (where the type represents the number of items in the folder).
      */
-    public int addItem(int type, int screen, int container, int x, int y) {
+    public int addItem(int type, int screen, int container, int x, int y, long profileId) {
         Context context = RuntimeEnvironment.application;
         int id = LauncherSettings.Settings.call(context.getContentResolver(),
                 LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
@@ -243,6 +252,7 @@
         values.put(LauncherSettings.Favorites.CELLY, y);
         values.put(LauncherSettings.Favorites.SPANX, 1);
         values.put(LauncherSettings.Favorites.SPANY, 1);
+        values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
 
         if (type == APP_ICON || type == SHORTCUT) {
             values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
@@ -253,11 +263,11 @@
                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
             // Add folder items.
             for (int i = 0; i < type; i++) {
-                addItem(APP_ICON, 0, id, 0, 0);
+                addItem(APP_ICON, 0, id, 0, 0, profileId);
             }
         }
 
-        context.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+        context.getContentResolver().insert(CONTENT_URI, values);
         return id;
     }
 
@@ -265,6 +275,15 @@
         return createGrid(typeArray, 1);
     }
 
+    public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+        final Context context = RuntimeEnvironment.application;
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+        return createGrid(typeArray, startScreen, mDefaultProfileId);
+    }
+
     /**
      * Initializes the DB with dummy elements to represent the provided grid structure.
      * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
@@ -273,14 +292,9 @@
      * @param startScreen First screen id from where the icons will be added.
      * @return the same grid representation where each entry is the corresponding item id.
      */
-    public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+    public int[][][] createGrid(int[][][] typeArray, int startScreen, long profileId) {
         Context context = RuntimeEnvironment.application;
-        LauncherSettings.Settings.call(context.getContentResolver(),
-                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-        LauncherSettings.Settings.call(context.getContentResolver(),
-                LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
         int[][][] ids = new int[typeArray.length][][];
-
         for (int i = 0; i < typeArray.length; i++) {
             // Add screen to DB
             int screenId = startScreen + i;
@@ -297,7 +311,8 @@
                         // Empty cell
                         ids[i][y][x] = -1;
                     } else {
-                        ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
+                        ids[i][y][x] = addItem(
+                                typeArray[i][y][x], screenId, DESKTOP, x, y, profileId);
                     }
                 }
             }
@@ -357,5 +372,9 @@
             createDbIfNotExists();
             return mOpenHelper.getWritableDatabase();
         }
+
+        public DatabaseHelper getHelper() {
+            return mOpenHelper;
+        }
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
index b8fff9c..6277c66 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
@@ -18,12 +18,13 @@
 import static org.mockito.Mockito.mock;
 
 import com.android.launcher3.shadows.LShadowAppWidgetManager;
+import com.android.launcher3.shadows.LShadowBackupManager;
 import com.android.launcher3.shadows.LShadowBitmap;
 import com.android.launcher3.shadows.LShadowLauncherApps;
 import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.shadows.ShadowDeviceFlag;
 import com.android.launcher3.shadows.ShadowLooperExecutor;
 import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
-import com.android.launcher3.shadows.ShadowDeviceFlag;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 
 import org.junit.runners.model.InitializationError;
@@ -47,7 +48,7 @@
             LShadowUserManager.class,
             LShadowLauncherApps.class,
             LShadowBitmap.class,
-
+            LShadowBackupManager.class,
             ShadowLooperExecutor.class,
             ShadowMainThreadInitializedObject.class,
             ShadowDeviceFlag.class,
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 5b453c3..d64967b 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -21,15 +21,11 @@
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
-import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.util.UiThreadHelper;
 
-import java.util.List;
-
 
 /**
  * The edit text that reports back when the back key has been pressed.
@@ -105,25 +101,6 @@
         UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
     }
 
-    @Override
-    public void onCommitCompletion(CompletionInfo text) {
-        setText(text.getText());
-        setSelection(text.getText().length());
-    }
-
-    /**
-     * Currently only used for folder name suggestion.
-     */
-    public void displayCompletions(List<String> suggestList) {
-        int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
-        CompletionInfo[] cInfo = new CompletionInfo[cnt];
-        for (int i = 0; i < cnt; i++) {
-            cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
-        }
-        post(() -> getContext().getSystemService(InputMethodManager.class)
-                .displayCompletions(this, cInfo));
-    }
-
     private boolean showSoftInput() {
         return requestFocus() &&
                 getContext().getSystemService(InputMethodManager.class)
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index e2b7b68..0fea0dc 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -45,6 +45,8 @@
      */
     public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
 
+    public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
+
     public int options;
 
     /**
@@ -140,4 +142,10 @@
             writer.updateItemInDatabase(this);
         }
     }
+
+    @Override
+    protected String dumpProperties() {
+        return super.dumpProperties()
+                + " manuallyTypedTitle=" + hasOption(FLAG_MANUAL_FOLDER_NAME);
+    }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8bfc028..7f443b6 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -464,12 +464,14 @@
     }
 
     public static SharedPreferences getPrefs(Context context) {
-        return context.getSharedPreferences(
+        // Use application context for shared preferences, so that we use a single cached instance
+        return context.getApplicationContext().getSharedPreferences(
                 LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
     public static SharedPreferences getDevicePrefs(Context context) {
-        return context.getSharedPreferences(
+        // Use application context for shared preferences, so that we use a single cached instance
+        return context.getApplicationContext().getSharedPreferences(
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index c73a5e1..670363e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -39,7 +39,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -79,6 +78,7 @@
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
@@ -2606,11 +2606,10 @@
 
         int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
-        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
-                Bitmap.Config.ARGB_8888);
         layout.measure(width, height);
         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
-        layout.draw(new Canvas(b));
+        Bitmap b = BitmapRenderer.createHardwareBitmap(
+                unScaledSize[0], unScaledSize[1], layout::draw);
         layout.setVisibility(visibility);
         return b;
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 13b7b54..27668eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
@@ -58,9 +59,9 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.BottomUserEducationView;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.views.WorkEduView;
 
 /**
  * The all apps view container.
@@ -92,6 +93,8 @@
     private boolean mUsingTabs;
     private boolean mSearchModeWhileUsingTabs = false;
 
+    private LauncherStateManager.StateListener mWorkTabListener;
+
     private RecyclerViewFastScroller mTouchHandler;
     private final Point mFastScrollerOffset = new Point();
 
@@ -287,7 +290,8 @@
     }
 
     @Override
-    public void onDropCompleted(View target, DragObject d, boolean success) { }
+    public void onDropCompleted(View target, DragObject d, boolean success) {
+    }
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
@@ -371,6 +375,7 @@
             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
             onTabChanged(mViewPager.getNextPage());
+            mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
         } else {
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
             mAH[AdapterHolder.WORK].recyclerView = null;
@@ -416,10 +421,9 @@
                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
             findViewById(R.id.tab_work)
                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
-
-        }
-        if (pos == AdapterHolder.WORK) {
-            BottomUserEducationView.showIfNeeded(mLauncher);
+            if (pos == AdapterHolder.WORK) {
+                WorkEduView.showWorkEduIfNeeded(mLauncher);
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
index e36f607..a9389bc 100644
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -2,7 +2,6 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -11,6 +10,7 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.widget.WidgetCell;
 
 /**
@@ -88,11 +88,9 @@
             bitmapHeight = viewHeight;
         }
 
-        Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(preview);
-        c.scale(scale, scale);
-        v.draw(c);
-        c.setBitmap(null);
-        return preview;
+        return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
+            c.scale(scale, scale);
+            v.draw(c);
+        });
     }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 844189f..90d8125 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -96,7 +96,7 @@
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
         View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
-
+    private static final boolean DEBUG = false;
     /**
      * We avoid measuring {@link #mContent} with a 0 width or height, as this
      * results in CellLayout being measured as UNSPECIFIED, which it does not support.
@@ -146,7 +146,7 @@
     @Thunk FolderIcon mFolderIcon;
 
     @Thunk FolderPagedView mContent;
-    public ExtendedEditText mFolderName;
+    public FolderNameEditText mFolderName;
     private PageIndicatorDots mPageIndicator;
 
     protected View mFooter;
@@ -306,6 +306,7 @@
                     mFolderName.setText(suggestedNames[0]);
                     mFolderName.displayCompletions(Arrays.asList(suggestedNames).subList(1,
                             suggestedNames.length));
+                    mFolderName.setEnteredCompose(false);
                 }
             }
             mFolderName.setHint("");
@@ -318,7 +319,13 @@
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
         String newTitle = mFolderName.getText().toString();
+        if (DEBUG) {
+            Log.d(TAG, "onBackKey newTitle=" + newTitle);
+        }
+
         mInfo.title = newTitle;
+        mInfo.setOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+                mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
 
@@ -350,6 +357,10 @@
     }
 
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (DEBUG) {
+            Log.d(TAG, "onEditorAction actionId=" + actionId + " key="
+                    + (event != null ? event.getKeyCode() : "null event"));
+        }
         if (actionId == EditorInfo.IME_ACTION_DONE) {
             mFolderName.dispatchBackKey();
             return true;
@@ -436,7 +447,8 @@
      */
     public void showSuggestedTitle(String[] suggestName) {
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get()
-                && TextUtils.isEmpty(mFolderName.getText().toString())) {
+                && TextUtils.isEmpty(mFolderName.getText().toString())
+                && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
             if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
                 mFolderName.setHint("");
                 mFolderName.setText(suggestName[0]);
@@ -552,9 +564,6 @@
             openFolder.close(true);
         }
 
-        if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mLauncher.getFolderNameProvider().load(getContext());
-        }
         mContent.bindItems(items);
         centerAboutIcon();
         mItemsInvalidated = true;
@@ -1495,6 +1504,9 @@
         return ContainerType.FOLDER;
     }
 
+    /**
+     * Navigation bar back key or hardware input back key has been issued.
+     */
     @Override
     public boolean onBackPressed() {
         if (isEditingName()) {
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
new file mode 100644
index 0000000..7e3f847
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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.folder;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.ExtendedEditText;
+
+import java.util.List;
+
+/**
+ * Handles additional edit text functionality to better support folder name suggestion.
+ * First, makes suggestion to the InputMethodManager via {@link #displayCompletions(List)}
+ * Second, intercepts whether user accepted the suggestion or manually edited their
+ * folder names.
+ */
+public class FolderNameEditText extends ExtendedEditText {
+    private static final String TAG = "FolderNameEditText";
+    private static final boolean DEBUG = false;
+
+    private boolean mEnteredCompose = false;
+
+    public FolderNameEditText(Context context) {
+        super(context);
+    }
+
+    public FolderNameEditText(Context context, AttributeSet attrs) {
+        // ctor chaining breaks the touch handling
+        super(context, attrs);
+    }
+
+    public FolderNameEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        InputConnection con = super.onCreateInputConnection(outAttrs);
+        FolderNameEditTextInputConnection connectionWrapper =
+                new FolderNameEditTextInputConnection(con, true);
+        return connectionWrapper;
+    }
+
+    public void displayCompletions(List<String> suggestList) {
+        int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
+        CompletionInfo[] cInfo = new CompletionInfo[cnt];
+        for (int i = 0; i < cnt; i++) {
+            cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
+        }
+        post(() -> getContext().getSystemService(InputMethodManager.class)
+                .displayCompletions(this, cInfo));
+    }
+
+    /**
+     * Within 's', the 'count' characters beginning at 'start' have just replaced
+     * old text 'before'
+     */
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        String reason = "unknown";
+        if (start == 0 && count == 0 && before > 0) {
+            reason = "suggestion was rejected";
+            mEnteredCompose = true;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "onTextChanged " + start + "," + before + "," + count
+                    + ", " + reason);
+        }
+    }
+
+    @Override
+    public void onCommitCompletion(CompletionInfo text) {
+        setText(text.getText());
+        setSelection(text.getText().length());
+        mEnteredCompose = false;
+    }
+
+    protected void setEnteredCompose(boolean value) {
+        mEnteredCompose = value;
+    }
+
+    protected boolean isEnteredCompose() {
+        if (DEBUG) {
+            Log.d(TAG, "isEnteredCompose " + mEnteredCompose);
+        }
+        return mEnteredCompose;
+    }
+
+    private class FolderNameEditTextInputConnection extends InputConnectionWrapper {
+
+        FolderNameEditTextInputConnection(InputConnection target, boolean mutable) {
+            super(target, mutable);
+        }
+
+        @Override
+        public boolean setComposingText(CharSequence cs, int newCursorPos) {
+            mEnteredCompose = true;
+            return super.setComposingText(cs, newCursorPos);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 782b0e2..9ea292c 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -109,11 +109,14 @@
         if (contains(candidatesOut, candidate)) {
             return;
         }
+
         for (int i = 0; i < candidate.length(); i++) {
             if (TextUtils.isEmpty(candidatesOut[i])) {
                 candidatesOut[i] = candidate;
+                return;
             }
         }
+        candidatesOut[candidate.length() - 1] = candidate;
     }
 
     private boolean contains(CharSequence[] list, CharSequence key) {
diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
index f10b972..d8a7070 100644
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ b/src/com/android/launcher3/graphics/ShadowDrawable.java
@@ -120,36 +120,35 @@
     }
 
     private void regenerateBitmapCache() {
-        Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
-                Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-
         // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
         Drawable d = mState.mChildState.newDrawable().mutate();
         d.setBounds(mState.mShadowSize, mState.mShadowSize,
                 mState.mIntrinsicWidth - mState.mShadowSize,
                 mState.mIntrinsicHeight - mState.mShadowSize);
         d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
-        d.draw(canvas);
 
-        // Do not draw shadow on dark theme
-        if (!mState.mIsDark) {
+        if (mState.mIsDark) {
+            // Dark text do not have any shadow, but just the bitmap
+            mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
+                    mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw);
+        } else {
             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
             paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
+
+            // Generate the shadow bitmap
             int[] offset = new int[2];
-            Bitmap shadow = bitmap.extractAlpha(paint, offset);
+            Bitmap shadow = BitmapRenderer.createSoftwareBitmap(
+                    mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw)
+                    .extractAlpha(paint, offset);
 
             paint.setMaskFilter(null);
             paint.setColor(mState.mShadowColor);
-            bitmap.eraseColor(Color.TRANSPARENT);
-            canvas.drawBitmap(shadow, offset[0], offset[1], paint);
-            d.draw(canvas);
+            mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
+                    mState.mIntrinsicWidth, mState.mIntrinsicHeight, c -> {
+                        c.drawBitmap(shadow, offset[0], offset[1], paint);
+                        d.draw(c);
+                    });
         }
-
-        if (BitmapRenderer.USE_HARDWARE_BITMAP) {
-            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
-        }
-        mState.mLastDrawnBitmap = bitmap;
     }
 
     @Override
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 8020f15..5a1dcab 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -259,7 +259,7 @@
         }
     }
 
-    public Bitmap createDitheredAlphaMask() {
+    private Bitmap createDitheredAlphaMask() {
         DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
         int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
         int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
diff --git a/src/com/android/launcher3/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
deleted file mode 100644
index bdc69af..0000000
--- a/src/com/android/launcher3/views/BottomUserEducationView.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 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.views;
-
-import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
-
-    private static final String KEY_SHOWED_BOTTOM_USER_EDUCATION = "showed_bottom_user_education";
-
-    private static final int DEFAULT_CLOSE_DURATION = 200;
-
-    private final Rect mInsets = new Rect();
-
-    private View mCloseButton;
-
-    public BottomUserEducationView(Context context, AttributeSet attr) {
-        this(context, attr, 0);
-    }
-
-    public BottomUserEducationView(Context context, AttributeSet attrs,
-            int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mContent = this;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mCloseButton = findViewById(R.id.close_bottom_user_tip);
-        mCloseButton.setOnClickListener(view -> handleClose(true));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        setTranslationShift(mTranslationShift);
-        expandTouchAreaOfCloseButton();
-    }
-
-    @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return ContainerType.TIP;
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_ON_BOARD_POPUP) != 0;
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        // Extend behind left, right, and bottom insets.
-        int leftInset = insets.left - mInsets.left;
-        int rightInset = insets.right - mInsets.right;
-        int bottomInset = insets.bottom - mInsets.bottom;
-        mInsets.set(insets);
-        setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
-                getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        handleClose(animate, DEFAULT_CLOSE_DURATION);
-        if (animate) {
-            // We animate only when the user is visible, which is a proxy for an explicit
-            // close action.
-            mLauncher.getSharedPrefs().edit()
-                    .putBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, true).apply();
-            sendCustomAccessibilityEvent(
-                    BottomUserEducationView.this,
-                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    getContext().getString(R.string.bottom_work_tab_user_education_closed));
-        }
-    }
-
-    private void open(boolean animate) {
-        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
-            return;
-        }
-        mIsOpen = true;
-        if (animate) {
-            mOpenCloseAnimator.setValues(
-                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
-            mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-            mOpenCloseAnimator.start();
-        } else {
-            setTranslationShift(TRANSLATION_SHIFT_OPENED);
-        }
-    }
-
-    public static void showIfNeeded(Launcher launcher) {
-        if (launcher.getSharedPrefs().getBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, false)) {
-            return;
-        }
-
-        LayoutInflater layoutInflater = LayoutInflater.from(launcher);
-        BottomUserEducationView bottomUserEducationView =
-                (BottomUserEducationView) layoutInflater.inflate(
-                        R.layout.work_tab_bottom_user_education_view, launcher.getDragLayer(),
-                        false);
-        launcher.getDragLayer().addView(bottomUserEducationView);
-        bottomUserEducationView.open(true);
-    }
-
-    private void expandTouchAreaOfCloseButton() {
-        Rect hitRect = new Rect();
-        mCloseButton.getHitRect(hitRect);
-        hitRect.left -= mCloseButton.getWidth();
-        hitRect.top -= mCloseButton.getHeight();
-        hitRect.right += mCloseButton.getWidth();
-        hitRect.bottom += mCloseButton.getHeight();
-        View parent = (View) mCloseButton.getParent();
-        parent.setTouchDelegate(new TouchDelegate(hitRect, mCloseButton));
-    }
-}
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
new file mode 100644
index 0000000..c3186f6
--- /dev/null
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * On boarding flow for users right after setting up work profile
+ */
+public class WorkEduView extends AbstractSlideInView implements Insettable {
+
+    private static final int DEFAULT_CLOSE_DURATION = 200;
+    private static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+
+    private static final int WORK_EDU_NOT_STARTED = 0;
+    private static final int WORK_EDU_PERSONAL_APPS = 1;
+    private static final int WORK_EDU_WORK_APPS = 2;
+
+    private static LauncherStateManager.StateListener sStateListener;
+
+    private Rect mInsets = new Rect();
+    private View mViewWrapper;
+    private Button mProceedButton;
+    private TextView mContentText;
+    private AllAppsPagedView mAllAppsPagedView;
+    private int mNextWorkEduStep = WORK_EDU_PERSONAL_APPS;
+
+
+    public WorkEduView(Context context, AttributeSet attr) {
+        this(context, attr, 0);
+    }
+
+    public WorkEduView(Context context, AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContent = this;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        mLauncher.getSharedPrefs().edit().putInt(KEY_WORK_EDU_STEP, mNextWorkEduStep).apply();
+        handleClose(true, DEFAULT_CLOSE_DURATION);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // Since this is on-boarding popup, it is not a user controlled action.
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return LauncherLogProto.ContainerType.TIP;
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ON_BOARD_POPUP) != 0;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mViewWrapper = findViewById(R.id.view_wrapper);
+        mProceedButton = findViewById(R.id.proceed);
+        mContentText = findViewById(R.id.content_text);
+
+        if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
+            mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
+        }
+
+        mProceedButton.setOnClickListener(view -> {
+            if (mAllAppsPagedView != null) {
+                mAllAppsPagedView.snapToPage(AllAppsContainerView.AdapterHolder.WORK);
+            }
+            goToWorkTab(true);
+        });
+    }
+
+    private void goToWorkTab(boolean animate) {
+        mProceedButton.setText(R.string.work_profile_edu_accept);
+        if (animate) {
+            ObjectAnimator animator = ObjectAnimator.ofFloat(mContentText, ALPHA, 0);
+            animator.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    mContentText.setText(mLauncher.getString(R.string.work_profile_edu_work_apps));
+                    ObjectAnimator.ofFloat(mContentText, ALPHA, 1).start();
+                }
+            });
+            animator.start();
+        } else {
+            mContentText.setText(mLauncher.getString(R.string.work_profile_edu_work_apps));
+        }
+        mNextWorkEduStep = WORK_EDU_WORK_APPS;
+        mProceedButton.setOnClickListener(v -> handleClose(true));
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        int leftInset = insets.left - mInsets.left;
+        int rightInset = insets.right - mInsets.right;
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(leftInset, getPaddingTop(), rightInset, 0);
+        mViewWrapper.setPaddingRelative(mViewWrapper.getPaddingStart(),
+                mViewWrapper.getPaddingTop(), mViewWrapper.getPaddingEnd(), bottomInset);
+    }
+
+    private void show() {
+        mLauncher.getDragLayer().addView(this);
+        animateOpen();
+    }
+
+    private void goToFirstPage() {
+        if (mAllAppsPagedView != null) {
+            mAllAppsPagedView.snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
+        }
+    }
+
+    private void animateOpen() {
+        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mIsOpen = true;
+        mOpenCloseAnimator.setValues(
+                PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+        mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mOpenCloseAnimator.start();
+    }
+
+    /**
+     * Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
+     */
+    public static LauncherStateManager.StateListener showEduFlowIfNeeded(Launcher launcher,
+            @Nullable LauncherStateManager.StateListener oldListener) {
+        if (oldListener != null) {
+            launcher.getStateManager().removeStateListener(oldListener);
+        }
+        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
+                != WORK_EDU_NOT_STARTED) {
+            return null;
+        }
+
+        LauncherStateManager.StateListener listener = new LauncherStateManager.StateListener() {
+            @Override
+            public void onStateTransitionStart(LauncherState toState) {
+
+            }
+
+            @Override
+            public void onStateTransitionComplete(LauncherState finalState) {
+                if (finalState != LauncherState.ALL_APPS) return;
+                LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+                WorkEduView v = (WorkEduView) layoutInflater.inflate(
+                        R.layout.work_profile_edu, launcher.getDragLayer(),
+                        false);
+                v.show();
+                v.goToFirstPage();
+                launcher.getStateManager().removeStateListener(this);
+            }
+        };
+        launcher.getStateManager().addStateListener(listener);
+        return listener;
+    }
+
+    /**
+     * Shows work apps edu if user had dismissed full edu flow
+     */
+    public static void showWorkEduIfNeeded(Launcher launcher) {
+        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
+                != WORK_EDU_PERSONAL_APPS) {
+            return;
+        }
+        LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+        WorkEduView v = (WorkEduView) layoutInflater.inflate(
+                R.layout.work_profile_edu, launcher.getDragLayer(), false);
+        v.show();
+        v.goToWorkTab(false);
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 2c1883b..4b72882 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -59,6 +59,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mLauncherPid = 0;
         super.setUp();
         initialize(this);
         mLauncherPid = mLauncher.getPid();
@@ -66,7 +67,9 @@
 
     @After
     public void teardown() {
-        assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+        if (mLauncherPid != 0) {
+            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+        }
     }
 
     public static void initialize(AbstractLauncherUiTest test) throws Exception {
@@ -121,7 +124,7 @@
         mDevice.pressMenu();
         mDevice.waitForIdle();
         mLauncher.getOptionsPopupMenu().getMenuItem("Home settings")
-                        .launch(mDevice.getLauncherPackageName());
+                .launch(mDevice.getLauncherPackageName());
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 62e2a53..de9757f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -36,8 +36,8 @@
 import com.android.launcher3.testcomponent.WidgetConfigActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.Wait.Condition;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 59b861c..0246f95 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -41,8 +41,8 @@
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.testcomponent.RequestPinItemActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.Wait.Condition;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
deleted file mode 100644
index d85dd3a..0000000
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.launcher3.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.test.uiautomator.UiObject2;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public interface Condition {
-
-    boolean isTrue() throws Throwable;
-
-    /**
-     * Converts the condition to be run on UI thread.
-     */
-    static Condition runOnUiThread(final Condition condition) {
-        final LooperExecutor executor = MAIN_EXECUTOR;
-        return () -> {
-            final AtomicBoolean value = new AtomicBoolean(false);
-            final Throwable[] exceptions = new Throwable[1];
-            final CountDownLatch latch = new CountDownLatch(1);
-            executor.execute(() -> {
-                try {
-                    value.set(condition.isTrue());
-                } catch (Throwable e) {
-                    exceptions[0] = e;
-                }
-
-            });
-            latch.await(1, TimeUnit.SECONDS);
-            if (exceptions[0] != null) {
-                throw exceptions[0];
-            }
-            return value.get();
-        };
-    }
-
-    static Condition minChildCount(final UiObject2 obj, final int childCount) {
-        return () -> obj.getChildCount() >= childCount;
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 2ab1e00..fe6143c 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -55,4 +55,12 @@
         launcher.checkForAnomaly();
         Assert.fail(message.get());
     }
+
+    /**
+     * Interface representing a generic condition
+     */
+    public interface Condition {
+
+        boolean isTrue() throws Throwable;
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index 831685a..c553b9b 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -29,6 +29,11 @@
     }
 
     static int getBugForFailure(CharSequence exception, String testsStartTime) {
+        if ("com.google.android.setupwizard".equals(
+                UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
+            return 145935261;
+        }
+
         final String logSinceTestsStart;
         try {
             logSinceTestsStart =
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0a3462f..5555eab 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -68,6 +68,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -93,10 +94,13 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
     private static final int GESTURE_STEP_MS = 16;
+    private static final SimpleDateFormat DATE_TIME_FORMAT =
+            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
     private static long START_TIME = System.currentTimeMillis();
 
     static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
-            "[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9]"
+            "(?<time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] "
+                    + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
                     + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<event>.*)");
 
     private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
@@ -167,7 +171,7 @@
     // Not null when we are collecting expected events to compare with actual ones.
     private List<Pattern> mExpectedEvents;
 
-    private String mTimeBeforeFirstLogEvent;
+    private Date mStartRecordingTime;
     private boolean mCheckEventsForSuccessfulGestures = false;
 
     private static Pattern getTouchEventPattern(String action) {
@@ -1187,32 +1191,38 @@
     private List<String> getEvents() {
         final ArrayList<String> events = new ArrayList<>();
         try {
-            final String logcatTimeParameter =
-                    mTimeBeforeFirstLogEvent != null ? " -t " + mTimeBeforeFirstLogEvent : "";
             final String logcatEvents = mDevice.executeShellCommand(
-                    "logcat -d --pid=" + getPid() + logcatTimeParameter
+                    "logcat -d -v year --pid=" + getPid() + " -t "
+                            + DATE_TIME_FORMAT.format(mStartRecordingTime).replaceAll(" ", "")
                             + " -s " + TestProtocol.TAPL_EVENTS_TAG);
             final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
             while (matcher.find()) {
+                // Skip events before recording start time.
+                if (DATE_TIME_FORMAT.parse(matcher.group("time"))
+                        .compareTo(mStartRecordingTime) < 0) {
+                    continue;
+                }
+
                 events.add(matcher.group("event"));
             }
             return events;
         } catch (IOException e) {
             throw new RuntimeException(e);
+        } catch (ParseException e) {
+            throw new AssertionError(e);
         }
     }
 
     private void startRecordingEvents() {
         Assert.assertTrue("Already recording events", mExpectedEvents == null);
         mExpectedEvents = new ArrayList<>();
-        mTimeBeforeFirstLogEvent = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
-                .format(new Date())
-                .replaceAll(" ", "");
-        log("startRecordingEvents: " + mTimeBeforeFirstLogEvent);
+        mStartRecordingTime = new Date();
+        log("startRecordingEvents: " + DATE_TIME_FORMAT.format(mStartRecordingTime));
     }
 
     private void stopRecordingEvents() {
         mExpectedEvents = null;
+        mStartRecordingTime = null;
     }
 
     Closable eventsCheck() {