Merge "Clean up work profile" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index fc99880..cb695df 100644
--- a/Android.bp
+++ b/Android.bp
@@ -32,7 +32,7 @@
 }
 
 java_library_static {
-    name: "launcher-log-protos-lite",
+    name: "launcher_log_protos_lite",
     srcs: [
         "protos/*.proto",
         "proto_overrides/*.proto",
@@ -45,4 +45,5 @@
             "proto_overrides",
         ],
     },
+    static_libs: ["libprotobuf-java-lite"],
 }
diff --git a/Android.mk b/Android.mk
index 66ccae0..c066a12 100644
--- a/Android.mk
+++ b/Android.mk
@@ -48,7 +48,9 @@
     androidx.preference_preference \
     iconloader_base
 
-LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    LauncherPluginLib \
+    launcher_log_protos_lite
 
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, protos) \
@@ -144,7 +146,10 @@
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    SystemUISharedLib \
+    launcherprotosnano \
+    launcher_log_protos_lite
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
@@ -213,7 +218,10 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    SystemUISharedLib \
+    launcherprotosnano \
+    launcher_log_protos_lite
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 26698eb..d747bb8 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -44,6 +44,7 @@
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
 
     <!--
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 3c7f308..ec1d55b 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -58,6 +58,35 @@
   optional TipType tip_type = 17;
   optional int32 search_query_length = 18;
   optional bool is_work_app = 19;
+  optional FromFolderLabelState from_folder_label_state = 20;
+  optional ToFolderLabelState to_folder_label_state = 21;
+
+  // Note: proto does not support duplicate enum values, even if they belong to different enum type.
+  // Hence "FROM" and "TO" prefix added.
+  enum FromFolderLabelState{
+    FROM_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+    FROM_EMPTY = 1;
+    FROM_CUSTOM = 2;
+    FROM_SUGGESTED = 3;
+  }
+
+  enum ToFolderLabelState{
+    TO_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+    TO_SUGGESTION0_WITH_VALID_PRIMARY = 1;
+    TO_SUGGESTION1_WITH_VALID_PRIMARY = 2;
+    TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 3;
+    TO_SUGGESTION2_WITH_VALID_PRIMARY = 4;
+    TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 5;
+    TO_SUGGESTION3_WITH_VALID_PRIMARY = 6;
+    TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 7;
+    TO_EMPTY_WITH_VALID_SUGGESTIONS = 8;
+    TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 9;
+    TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 10;
+    TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11;
+    TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 12;
+    TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 13;
+    UNCHANGED = 14;
+  }
 }
 
 // Used to define what type of item a Target would represent.
@@ -141,7 +170,8 @@
     AUTOMATED = 1;
     COMMAND = 2;
     TIP = 3;
-    // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
+    SOFT_KEYBOARD = 4;
+    // HARD_KEYBOARD, ASSIST
   }
 
   enum Touch {
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 5d871c3..d3cec28 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -24,6 +24,7 @@
 
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
     <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index f82af62..834e6cf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -296,7 +296,7 @@
                 predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
                 predictedApps.add(predictedApp);
             } else {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                if (FeatureFlags.IS_STUDIO_BUILD) {
                     Log.e(TAG, "Predicted app not found: " + mapper);
                 }
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 482348f..345a147 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -917,6 +917,12 @@
             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
+                    if (mRecentsAnimationController == null) {
+                        // If the recents animation is interrupted, we still end the running
+                        // animation (not canceled) so this is still called. In that case, we can
+                        // skip doing any future work here for the current gesture.
+                        return;
+                    }
                     // Finalize the state and notify of the change
                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
                 }
@@ -938,6 +944,12 @@
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
+                    if (mRecentsAnimationController == null) {
+                        // If the recents animation is interrupted, we still end the running
+                        // animation (not canceled) so this is still called. In that case, we can
+                        // skip doing any future work here for the current gesture.
+                        return;
+                    }
                     if (target == NEW_TASK && mRecentsView != null
                             && mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) {
                         // We are about to launch the current running task, so use LAST_TASK state
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 321af6c..ef1698e 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
@@ -1251,7 +1251,7 @@
     }
 
     public PendingAnimation createAllTasksDismissAnimation(long duration) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
         AnimatorSet anim = new AnimatorSet();
@@ -1595,7 +1595,7 @@
     }
 
     public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
 
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index 6c65e01..b3875ae 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -130,7 +130,7 @@
             final LinkedList<Runnable> callbacks;
             if (mCallbacks.indexOfKey(stateMask) >= 0) {
                 callbacks = mCallbacks.get(stateMask);
-                if (FeatureFlags.IS_DOGFOOD_BUILD && callbacks.contains(callback)) {
+                if (FeatureFlags.IS_STUDIO_BUILD && callbacks.contains(callback)) {
                     throw new IllegalStateException("Existing callback for state found");
                 }
             } else {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index acf61b4..0f98b32 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -62,6 +62,12 @@
         mListeners.remove(listener);
     }
 
+    @UiThread
+    public void removeAllListeners() {
+        Preconditions.assertUIThread();
+        mListeners.clear();
+    }
+
     public void notifyAnimationCanceled() {
         mCancelled = true;
         onAnimationCanceled(null);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d0afb21..4b33d21 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -180,7 +180,7 @@
             mDefaultDisplay.addChangeListener(this);
         }
 
-        if (mMode == NO_BUTTON) {
+        if (newMode == NO_BUTTON) {
             mExclusionListener.register();
         } else {
             mExclusionListener.unregister();
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index e3e8ace..6902e37 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -55,7 +55,7 @@
         // Notify if recents animation is still running
         if (mController != null) {
             String msg = "New recents animation started before old animation completed";
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw new IllegalArgumentException(msg);
             } else {
                 Log.e("TaskAnimationManager", msg, new Exception());
@@ -156,9 +156,9 @@
             mTargets.release();
         }
 
-        // Remove gesture state from callbacks
-        if (mCallbacks != null && mLastGestureState != null) {
-            mCallbacks.removeListener(mLastGestureState);
+        // Clean up all listeners to ensure we don't get subsequent callbacks
+        if (mCallbacks != null) {
+            mCallbacks.removeAllListeners();
         }
 
         mController = null;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
index 295ab48..d2a0951 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
@@ -16,7 +16,10 @@
 package com.android.quickstep.interaction;
 
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.View;
 import android.view.Window;
 
@@ -26,6 +29,7 @@
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
 
+import java.util.List;
 import java.util.Optional;
 
 /** Shows the Back gesture interactive tutorial in full screen mode. */
@@ -47,6 +51,12 @@
     }
 
     @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        disableSystemGestures();
+    }
+
+    @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
         if (hasFocus) {
@@ -70,4 +80,14 @@
                         | View.SYSTEM_UI_FLAG_FULLSCREEN);
         getWindow().setNavigationBarColor(Color.TRANSPARENT);
     }
+
+    private void disableSystemGestures() {
+        Display display = getDisplay();
+        if (display != null) {
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            getWindow().setSystemGestureExclusionRects(
+                    List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/BinderTracker.java b/quickstep/src/com/android/quickstep/util/BinderTracker.java
index 32d0d53..cb04e5b 100644
--- a/quickstep/src/com/android/quickstep/util/BinderTracker.java
+++ b/quickstep/src/com/android/quickstep/util/BinderTracker.java
@@ -31,7 +31,7 @@
     private static final String TAG = "BinderTracker";
 
     public static void start() {
-        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (!FeatureFlags.IS_STUDIO_BUILD) {
             Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
             return;
         }
@@ -40,7 +40,7 @@
     }
 
     public static void stop() {
-        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (!FeatureFlags.IS_STUDIO_BUILD) {
             Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 9cf55b3..1d8a79f 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -84,6 +84,7 @@
         float taskWidth, taskHeight, paddingHorz;
         Resources res = context.getResources();
         Rect insets = dp.getInsets();
+        final boolean overviewActionsEnabled = ENABLE_OVERVIEW_ACTIONS.get();
 
         if (dp.isMultiWindowMode) {
             if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
@@ -113,7 +114,7 @@
             final int paddingResId;
             if (dp.isVerticalBarLayout()) {
                 paddingResId = R.dimen.landscape_task_card_horz_space;
-            } else if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+            } else if (overviewActionsEnabled && removeShelfFromOverview(context)) {
                 paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
             } else {
                 paddingResId = R.dimen.portrait_task_card_horz_space;
@@ -121,9 +122,11 @@
             paddingHorz = res.getDimension(paddingResId);
         }
 
-        float topIconMargin =   res.getDimension(R.dimen.task_thumbnail_top_margin);
+        float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
         float bottomMargin = thumbnailBottomMargin(context);
-        float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
+
+        float paddingVert = overviewActionsEnabled && removeShelfFromOverview(context)
+                ? 0 : res.getDimension(R.dimen.task_card_vert_space);
 
         // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
         // we override the insets ourselves.
@@ -141,7 +144,7 @@
         // Center in the visible space
         float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
         float y = insets.top + Math.max(topIconMargin,
-                (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
+                (launcherVisibleHeight - extraVerticalSpace - outHeight - bottomMargin) / 2);
         outRect.set(Math.round(x), Math.round(y),
                 Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 3b3e1c7..724af66 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -47,7 +47,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -259,10 +258,8 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    // b/143285809 Remove @Stability on 02/27/20 if the test doesn't flake.
     @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
-    // b/143285809
-    @Ignore
     public void testQuickSwitchFromApp() throws Exception {
         startTestActivity(2);
         startTestActivity(3);
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index c02753f..1c18076 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -23,14 +23,14 @@
     <ImageView
         android:id="@+id/icon"
         android:contentDescription="@string/work_apps_paused_title"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:tint="?attr/folderTextColor"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:tint="?attr/workProfileOverlayTextColor"
         android:src="@drawable/ic_corp_off" />
 
     <TextView
         style="@style/TextHeadline"
-        android:textColor="?attr/folderTextColor"
+        android:textColor="?attr/workProfileOverlayTextColor"
         android:id="@+id/work_apps_paused_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -43,7 +43,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="?attr/folderTextColor"
+        android:textColor="?attr/workProfileOverlayTextColor"
         android:text="@string/work_apps_paused_body"
         android:textAlignment="center"
         android:textSize="16sp" />
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index db95416..2cffedd 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -32,6 +32,8 @@
         android:layout_weight="1"
         android:drawableStart="@drawable/ic_corp"
         android:drawablePadding="3dp"
+        android:drawableTint="?attr/workProfileOverlayTextColor"
+        android:textColor="?attr/workProfileOverlayTextColor"
         android:layout_height="wrap_content"
         android:ellipsize="end"
         android:gravity="center_vertical"
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index aef878b..5a15ec6 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -41,6 +41,7 @@
     <attr name="folderIconBorderColor" format="color" />
     <attr name="folderTextColor" format="color" />
     <attr name="folderHintColor" format="color" />
+    <attr name="workProfileOverlayTextColor" format="color" />
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5faa429..9d0fb56 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -142,7 +142,7 @@
     <string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string>
 
     <!-- Default folder title -->
-    <string name="folder_hint_text">Tap to edit</string>
+    <string name="folder_hint_text">Edit Name</string>
 
     <!-- Accessibility -->
     <!-- The format string for when an app is temporarily disabled. -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1174a2f..cee268b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -50,6 +50,7 @@
         <item name="folderTextColor">#FF212121</item>
         <item name="folderHintColor">#FF616161</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
+        <item name="workProfileOverlayTextColor">#FF212121</item>
 
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
@@ -77,6 +78,7 @@
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="folderTextColor">?attr/workspaceTextColor</item>
+
     </style>
 
     <style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
@@ -100,6 +102,7 @@
         <item name="folderHintColor">#FFCCCCCC</item>
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
+        <item name="workProfileOverlayTextColor">@android:color/white</item>
     </style>
 
     <style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index ac61c49..8718820 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -538,7 +538,7 @@
         try {
             dispatchRestoreInstanceState(states);
         } catch (IllegalArgumentException ex) {
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw ex;
             }
             // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 466b7b2..f07040d 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -96,7 +96,7 @@
             }
 
             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                if (FeatureFlags.IS_STUDIO_BUILD) {
                     throw new IllegalStateException("Parent of the focused item is not supported.");
                 } else {
                     return false;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 94aadb2..3fc8de2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2143,7 +2143,7 @@
                     Object tag = v.getTag();
                     String desc = "Collision while binding workspace item: " + item
                             + ". Collides with " + tag;
-                    if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                    if (FeatureFlags.IS_STUDIO_BUILD) {
                         throw (new RuntimeException(desc));
                     } else {
                         Log.d(TAG, desc);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4cd038d..d77285d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -91,7 +91,7 @@
                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (FeatureFlags.IS_STUDIO_BUILD) {
             mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
         }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index bb6c330..f618fe1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
@@ -55,7 +55,6 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LooperExecutor;
@@ -67,7 +66,6 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Optional;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
@@ -128,16 +126,6 @@
     }
 
     /**
-     * Returns AppInfo with corresponding package name.
-     * TODO: move to enqueueModelTask
-     */
-    public Optional<AppInfo> getAppInfoByPackageName(String pkg) {
-        return mBgAllAppsList.data.stream()
-                .filter(info -> info.componentName.getPackageName().equals(pkg))
-                .findAny();
-    }
-
-    /**
      * Adds the provided items to the workspace.
      */
     public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
@@ -250,7 +238,7 @@
                     enqueueModelUpdateTask(new UserLockStateChangedTask(user));
                 }
             }
-        } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
+        } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
             for (Callbacks cb : getCallbacks()) {
                 if (cb instanceof Launcher) {
                     ((Launcher) cb).recreate();
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 5544240..697048a 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -119,7 +119,7 @@
 
     @Override
     public boolean onCreate() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (FeatureFlags.IS_STUDIO_BUILD) {
             Log.d(TAG, "Launcher process started");
         }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index ae4eae9..4c6e1f3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1502,7 +1502,7 @@
             return false;
         }
 
-        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (FeatureFlags.IS_STUDIO_BUILD) {
             duration *= Settings.Global.getFloat(getContext().getContentResolver(),
                     Settings.Global.WINDOW_ANIMATION_SCALE, 1);
         }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b7f8547..a8f492f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1858,7 +1858,7 @@
                         CellLayout parentCell = getParentCellLayoutForView(cell);
                         if (parentCell != null) {
                             parentCell.removeView(cell);
-                        } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                        } else if (FeatureFlags.IS_STUDIO_BUILD) {
                             throw new NullPointerException("mDragInfo.cell has null parent");
                         }
                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
@@ -2153,7 +2153,7 @@
 
         ItemInfo item = d.dragInfo;
         if (item == null) {
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw new NullPointerException("DragObject has null info");
             }
             return;
@@ -2775,7 +2775,7 @@
                     mDragInfo.container, mDragInfo.screenId);
             if (cellLayout != null) {
                 cellLayout.onDropChild(mDragInfo.cell);
-            } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            } else if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw new RuntimeException("Invalid state: cellLayout == null in "
                         + "Workspace#onDropCompleted. Please file a bug. ");
             }
@@ -2794,7 +2794,7 @@
         CellLayout parentCell = getParentCellLayoutForView(v);
         if (parentCell != null) {
             parentCell.removeView(v);
-        } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        } else if (FeatureFlags.IS_STUDIO_BUILD) {
             // When an app is uninstalled using the drop target, we wait until resume to remove
             // the icon. We also remove all the corresponding items from the workspace at
             // {@link Launcher#bindComponentsRemoved}. That call can come before or after
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e66cf39..fcccc59 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -155,6 +155,11 @@
         return mMultiValueAlpha.getProperty(index);
     }
 
+    public WorkFooterContainer getWorkFooterContainer() {
+        return mWorkFooterContainer;
+    }
+
+
     @Override
     protected void setDampedScrollShift(float shift) {
         // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ea549b9..6413044 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -43,7 +43,10 @@
         return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
     }
 
-    public static final boolean IS_DOGFOOD_BUILD = BuildConfig.DEBUG;
+    /**
+     * True when the build has come from Android Studio and is being used for local debugging.
+     */
+    public static final boolean IS_STUDIO_BUILD = BuildConfig.DEBUG;
 
     /**
      * Enable moving the QSB on the 0th screen of the workspace. This is not a configuration feature
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1b7b015..de7bc6d 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -160,8 +160,8 @@
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
-            mMotionDownX = mOptions.systemDndStartPoint.x;
-            mMotionDownY = mOptions.systemDndStartPoint.y;
+            mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x;
+            mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y;
         }
 
         final int registrationX = mMotionDownX - dragLayerX;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 544efd5..1d79e01 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -16,9 +16,23 @@
 
 package com.android.launcher3.folder;
 
+import static android.text.TextUtils.isEmpty;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import static com.android.launcher3.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -75,22 +89,24 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.userevent.LauncherLogProto.Action;
+import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
+import com.android.launcher3.userevent.LauncherLogProto.Target;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ClipPathView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -188,6 +204,9 @@
     @Thunk int mScrollHintDir = SCROLL_NONE;
     @Thunk int mCurrentScrollDir = SCROLL_NONE;
 
+    private String mPreviousLabel;
+    private boolean mIsPreviousLabelSuggested;
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -302,12 +321,12 @@
     public void startEditingFolderName() {
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                if (TextUtils.isEmpty(mFolderName.getText())) {
+                if (isEmpty(mFolderName.getText())) {
                     FolderNameInfo[] nameInfos =
                             (FolderNameInfo[]) mInfo.suggestedFolderNames.getParcelableArrayExtra(
                                     FolderInfo.EXTRA_FOLDER_SUGGESTIONS);
                     if (nameInfos != null) {
-                        showLabelSuggestion(nameInfos);
+                        showLabelSuggestion(nameInfos, false);
                     }
                 }
             }
@@ -326,23 +345,16 @@
         }
 
         mInfo.title = newTitle;
-        mInfo.setOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
                 mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
 
-        if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mFolderName.setText(mInfo.title);
-            // TODO: depending on whether the title was manually edited or automatically
-            // suggested, apply different hint.
-            mFolderName.setHint("");
+        if (TextUtils.isEmpty(mInfo.title)) {
+            mFolderName.setHint(R.string.folder_hint_text);
+            mFolderName.setText("");
         } else {
-            if (TextUtils.isEmpty(mInfo.title)) {
-                mFolderName.setHint(R.string.folder_hint_text);
-                mFolderName.setText("");
-            } else {
-                mFolderName.setHint(null);
-            }
+            mFolderName.setHint(null);
         }
 
         sendCustomAccessibilityEvent(
@@ -425,8 +437,10 @@
         mItemsInvalidated = true;
         mInfo.addListener(this);
 
-        if (!TextUtils.isEmpty(mInfo.title)) {
+        if (!isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
+            mPreviousLabel = mInfo.title.toString();
+            mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
             mFolderName.setHint(null);
         } else {
             mFolderName.setText("");
@@ -452,9 +466,9 @@
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
             mInfo.suggestedFolderNames = new Intent().putExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
                     nameInfos);
-            if (TextUtils.isEmpty(mFolderName.getText().toString())
-                    && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
-                showLabelSuggestion(nameInfos);
+            if (isEmpty(mFolderName.getText().toString())
+                    && !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME)) {
+                showLabelSuggestion(nameInfos, true);
             }
         }
     }
@@ -463,28 +477,29 @@
      * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
      * InputMethodManager suggestions.
      */
-    private void showLabelSuggestion(FolderNameInfo[] nameInfos) {
+    private void showLabelSuggestion(FolderNameInfo[] nameInfos, boolean animate) {
         if (nameInfos == null) {
             return;
         }
         // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
         // string.
-        boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !TextUtils.isEmpty(
+        boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !isEmpty(
                 nameInfos[0].getLabel())
-                || nameInfos.length > 1 && nameInfos[1] != null && !TextUtils.isEmpty(
+                || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
                 nameInfos[1].getLabel());
         CharSequence firstLabel = nameInfos[0].getLabel();
 
         if (shouldOpen) {
-            if (!TextUtils.isEmpty(firstLabel)) {
+            if (!isEmpty(firstLabel)) {
                 mFolderName.setHint("");
                 mFolderName.setText(firstLabel);
-                mInfo.title = firstLabel;
             }
-            animateOpen(mInfo.contents, 0, true);
+            if (animate) {
+                animateOpen(mInfo.contents, 0, true);
+            }
             mFolderName.showKeyboard();
             mFolderName.displayCompletions(
-                    Arrays.asList(nameInfos).subList(1, nameInfos.length).stream()
+                    asList(nameInfos).subList(1, nameInfos.length).stream()
                             .filter(Objects::nonNull)
                             .map(s -> s.getLabel().toString())
                             .collect(Collectors.toList()));
@@ -608,7 +623,7 @@
             dragLayer.addView(this);
             mDragController.addDropTarget(this);
         } else {
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
                         + getParent());
             }
@@ -636,9 +651,9 @@
 
                 if (!skipUserEventLog) {
                     mLauncher.getUserEventDispatcher().logActionOnItem(
-                        Touch.TAP,
-                        Direction.NONE,
-                        ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+                            LauncherLogProto.Action.Touch.TAP,
+                            LauncherLogProto.Action.Direction.NONE,
+                            LauncherLogProto.ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
                 }
 
 
@@ -1420,6 +1435,7 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
+                logEditFolderLabel();
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1433,11 +1449,12 @@
     }
 
     @Override
-    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
         target.gridX = info.cellX;
         target.gridY = info.cellY;
         target.pageIndex = mContent.getCurrentPage();
-        targetParent.containerType = ContainerType.FOLDER;
+        targetParent.containerType = LauncherLogProto.ContainerType.FOLDER;
     }
 
     private class OnScrollHintListener implements OnAlarmListener {
@@ -1535,7 +1552,7 @@
 
     @Override
     public int getLogContainerType() {
-        return ContainerType.FOLDER;
+        return LauncherLogProto.ContainerType.FOLDER;
     }
 
     /**
@@ -1570,7 +1587,7 @@
                     }
                 } else {
                     mLauncher.getUserEventDispatcher().logActionTapOutside(
-                            LoggerUtils.newContainerTarget(ContainerType.FOLDER));
+                            LoggerUtils.newContainerTarget(LauncherLogProto.ContainerType.FOLDER));
                     close(true);
                     return true;
                 }
@@ -1600,4 +1617,113 @@
             super.draw(canvas);
         }
     }
+
+    private void logEditFolderLabel() {
+        LauncherEvent launcherEvent = LauncherEvent.newBuilder()
+                .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
+                .addSrcTarget(newEditTextTargetBuilder()
+                        .setFromFolderLabelState(getFromFolderLabelState())
+                        .setToFolderLabelState(getToFolderLabelState()))
+                .addSrcTarget(newFolderTargetBuilder())
+                .addSrcTarget(newParentContainerTarget())
+                .build();
+        mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent);
+        mPreviousLabel = mFolderName.getText().toString();
+        mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
+    }
+
+    private Target.FromFolderLabelState getFromFolderLabelState() {
+        return mPreviousLabel == null
+                ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED
+                : mPreviousLabel.isEmpty()
+                ? FROM_EMPTY
+                : mIsPreviousLabelSuggested
+                ? FROM_SUGGESTED
+                : FROM_CUSTOM;
+    }
+
+    private Target.ToFolderLabelState getToFolderLabelState() {
+        String newLabel =
+                checkNotNull(mFolderName.getText().toString(),
+                        "Expected valid folder label, but found null");
+
+        Optional<String[]> suggestedLabels = Optional.ofNullable(
+                (FolderNameInfo[]) mInfo.suggestedFolderNames
+                        .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+                .map(folderNameInfoArray ->
+                        stream(folderNameInfoArray)
+                                .filter(Objects::nonNull)
+                                .map(FolderNameInfo::getLabel)
+                                .map(CharSequence::toString)
+                                .toArray(String[]::new));
+
+
+        int accepted_suggestion_index = suggestedLabels
+                .map(folderNameInfoArray ->
+                        IntStream.range(0, folderNameInfoArray.length)
+                                .filter(index -> newLabel.equalsIgnoreCase(
+                                        folderNameInfoArray[index]))
+                                .findFirst()
+                                .orElse(-1)
+                ).orElse(-1);
+
+        boolean hasValidPrimary = suggestedLabels
+                .map(labels -> labels.length > 0 && !isEmpty(labels[0]))
+                .orElse(false);
+        String primarySuffix = hasValidPrimary
+                ? "_WITH_VALID_PRIMARY"
+                : "_WITH_EMPTY_PRIMARY";
+
+        boolean isEmptySuggestions = suggestedLabels
+                .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
+                .orElse(true);
+        boolean isSuggestionsEnabled = FeatureFlags.FOLDER_NAME_SUGGEST.get();
+        String suggestionsSuffix =  isSuggestionsEnabled
+                ? isEmptySuggestions
+                    ? "_WITH_EMPTY_SUGGESTIONS"
+                    : "_WITH_VALID_SUGGESTIONS"
+                : "_WITH_SUGGESTIONS_DISABLED";
+
+        return newLabel.equals(mPreviousLabel)
+                ? Target.ToFolderLabelState.UNCHANGED
+                : newLabel.isEmpty()
+                    ? Target.ToFolderLabelState.valueOf("TO_EMPTY" + suggestionsSuffix)
+                    : accepted_suggestion_index >= 0
+                        ? Target.ToFolderLabelState.valueOf("TO_SUGGESTION"
+                            + accepted_suggestion_index
+                            + primarySuffix)
+                        : Target.ToFolderLabelState.valueOf("TO_CUSTOM" + suggestionsSuffix);
+    }
+
+
+    private Target.Builder newEditTextTargetBuilder() {
+        return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
+    }
+
+    private Target.Builder newFolderTargetBuilder() {
+        return Target.newBuilder()
+                .setType(Target.Type.CONTAINER)
+                .setContainerType(ContainerType.FOLDER)
+                .setPageIndex(mInfo.screenId)
+                .setGridX(mInfo.cellX)
+                .setGridY(mInfo.cellY)
+                .setCardinality(mInfo.contents.size());
+    }
+
+    private Target.Builder newParentContainerTarget() {
+        Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
+
+        switch (mInfo.container) {
+            case CONTAINER_HOTSEAT:
+                return builder.setContainerType(ContainerType.HOTSEAT);
+            case CONTAINER_DESKTOP:
+                return builder.setContainerType(ContainerType.WORKSPACE);
+            default:
+                throw new AssertionError(String
+                        .format("Expected container to be either %s or %s but found %s.",
+                                CONTAINER_HOTSEAT,
+                                CONTAINER_DESKTOP,
+                                mInfo.container));
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index d5990fa..184dbb9 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -21,9 +21,14 @@
 import android.util.Log;
 
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.ArrayList;
@@ -50,6 +55,8 @@
      * name edit box can also be used to provide suggestion.
      */
     public static final int SUGGEST_MAX = 4;
+    protected IntSparseArrayMap<FolderInfo> mFolderInfos;
+    protected List<AppInfo> mAppInfos;
 
     /**
      * Retrieve instance of this object that can be overridden in runtime based on the build
@@ -58,9 +65,30 @@
     public static FolderNameProvider newInstance(Context context) {
         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
                 context.getApplicationContext(), R.string.folder_name_provider_class);
+        fnp.load(context);
+
         return fnp;
     }
 
+    public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
+            IntSparseArrayMap<FolderInfo> folderInfos) {
+        FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
+                context.getApplicationContext(), R.string.folder_name_provider_class);
+        fnp.load(appInfos, folderInfos);
+
+        return fnp;
+    }
+
+    private void load(Context context) {
+        LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(
+                new FolderNameWorker());
+    }
+
+    private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
+        mAppInfos = appInfos;
+        mFolderInfos = folderInfos;
+    }
+
     /**
      * Generate and rank the suggested Folder names.
      */
@@ -92,9 +120,8 @@
                 .collect(Collectors.toList());
 
         if (distinctItemInfos.size() == 1) {
-            Optional<AppInfo> info = LauncherAppState.getInstance(context).getModel()
-                    .getAppInfoByPackageName(distinctItemInfos.get(0).getTargetComponent()
-                            .getPackageName());
+            Optional<AppInfo> info = getAppInfoByPackageName(
+                    distinctItemInfos.get(0).getTargetComponent().getPackageName());
             // Place it as first viable suggestion and shift everything else
             info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
@@ -103,6 +130,15 @@
         }
     }
 
+    private Optional<AppInfo> getAppInfoByPackageName(String packageName) {
+        if (mAppInfos == null || mAppInfos.isEmpty()) {
+            return Optional.empty();
+        }
+        return mAppInfos.stream()
+                .filter(info -> info.componentName.getPackageName().equals(packageName))
+                .findAny();
+    }
+
     private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
         if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
@@ -143,4 +179,13 @@
         Map<Object, Boolean> map = new ConcurrentHashMap<>();
         return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
     }
+
+    private class FolderNameWorker extends BaseModelUpdateTask {
+        @Override
+        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            mFolderInfos = dataModel.folders.clone();
+            mAppInfos = Arrays.asList(apps.copyData());
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index f579451..94d30f6 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -152,7 +152,7 @@
     }
 
     public final void generateDragOutline(Bitmap preview) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mOutlineGeneratorCallback != null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && mOutlineGeneratorCallback != null) {
             throw new RuntimeException("Drag outline generated twice");
         }
 
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 8289da9..199d13f 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -69,8 +69,7 @@
 public class UserEventDispatcher implements ResourceBasedOverride {
 
     private static final String TAG = "UserEvent";
-    private static final boolean IS_VERBOSE =
-            FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+    private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.USEREVENT);
     private static final String UUID_STORAGE = "uuid";
 
     public static UserEventDispatcher newInstance(Context context,
@@ -372,6 +371,25 @@
         dispatchUserEvent(event, null);
     }
 
+    /**
+     * Logs proto lite version of LauncherEvent object to clearcut.
+     */
+    public void logLauncherEvent(
+                com.android.launcher3.userevent.LauncherLogProto.LauncherEvent launcherEvent) {
+
+        if (mPreviousHomeGesture) {
+            mPreviousHomeGesture = false;
+        }
+        mAppOrTaskLaunch = false;
+        launcherEvent.toBuilder()
+            .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
+            .setElapsedSessionMillis(SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
+        if (!IS_VERBOSE) {
+            return;
+        }
+        Log.d(TAG, launcherEvent.toString());
+    }
+
     public void logDeepShortcutsOpen(View icon) {
         LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(icon);
         if (icon == null || !(icon.getTag() instanceof ItemInfo || provider == null)) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index c24b939..32fce0b 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -273,7 +273,7 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                     folders.remove(item.id);
-                    if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                    if (FeatureFlags.IS_STUDIO_BUILD) {
                         for (ItemInfo info : itemsIdMap) {
                             if (info.container == item.id) {
                                 // We are deleting a folder which still contains items that
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index af802ef..6223a23 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -901,7 +901,8 @@
     }
 
     private void loadFolderNames() {
-        FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext());
+        FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
+                mBgAllAppsList.data, mBgDataModel.folders);
 
         synchronized (mBgDataModel) {
             for (int i = 0; i < mBgDataModel.folders.size(); i++) {
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 628dd95..1473124 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -97,7 +97,7 @@
                         return Integer.compare(lhs.screenId, rhs.screenId);
                     }
                     default:
-                        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                        if (FeatureFlags.IS_STUDIO_BUILD) {
                             throw new RuntimeException(
                                     "Unexpected container type when sorting workspace items.");
                         }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index ccd1554..27fa580 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -115,8 +115,9 @@
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
-            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
-                    modelItem instanceof WorkspaceItemInfo && item instanceof WorkspaceItemInfo) {
+            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_STUDIO_BUILD
+                    && modelItem instanceof WorkspaceItemInfo
+                    && item instanceof WorkspaceItemInfo) {
                 if (modelItem.title.toString().equals(item.title.toString()) &&
                         modelItem.getIntent().filterEquals(item.getIntent()) &&
                         modelItem.id == item.id &&
@@ -320,7 +321,7 @@
      */
     public void prepareToUndoDelete() {
         if (!mPreparingToUndo) {
-            if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_STUDIO_BUILD) {
                 throw new IllegalStateException("There are still uncommitted delete operations!");
             }
             mDeleteRunnables.clear();
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index 4a4a5ca..fcb96d7 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.io.ByteArrayOutputStream;
@@ -67,7 +66,7 @@
      * Utility method to debug binary data
      */
     public static String createTempFile(Context context, byte[] data) {
-        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (!FeatureFlags.IS_STUDIO_BUILD) {
             throw new IllegalStateException("Method only allowed in development mode");
         }
 
@@ -87,7 +86,7 @@
             try {
                 c.close();
             } catch (IOException e) {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                if (FeatureFlags.IS_STUDIO_BUILD) {
                     Log.d(TAG, "Error closing", e);
                 }
             }
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index ed66422..63d56e4 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -28,25 +28,25 @@
 public class Preconditions {
 
     public static void assertNotNull(Object o) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && o == null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && o == null) {
             throw new IllegalStateException();
         }
     }
 
     public static void assertWorkerThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
             throw new IllegalStateException();
         }
     }
 
     public static void assertUIThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !isSameLooper(Looper.getMainLooper())) {
             throw new IllegalStateException();
         }
     }
 
     public static void assertNonUiThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) {
+        if (FeatureFlags.IS_STUDIO_BUILD && isSameLooper(Looper.getMainLooper())) {
             throw new IllegalStateException();
         }
     }
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
index f8add9a..9ac8230 100644
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ b/src/com/android/launcher3/views/WorkFooterContainer.java
@@ -119,6 +119,13 @@
         mWorkModeSwitch.refresh();
     }
 
+    /**
+     * Returns work mode switch
+     */
+    public WorkModeSwitch getWorkModeSwitch() {
+        return mWorkModeSwitch;
+    }
+
     private boolean shouldShowWorkFooter() {
         Launcher launcher = Launcher.getLauncher(getContext());
         return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 5ebf8d3..282867a 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -115,7 +115,7 @@
             }
             setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
         } catch (Exception e) {
-            if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
+            if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
                 // the returned value may be incomplete and will not be refreshed until the next
                 // time Launcher starts.
                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
diff --git a/tests/Android.mk b/tests/Android.mk
index d1a6c06..a9fff8e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -51,7 +51,7 @@
     androidx.test.rules \
     androidx.test.uiautomator_uiautomator \
     mockito-target-minus-junit4 \
-    launcher-log-protos-lite
+    launcher_log_protos_lite
 
 ifneq (,$(wildcard frameworks/base))
     LOCAL_PRIVATE_PLATFORM_APIS := true
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index d9edc35..5aa0090 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,17 +16,32 @@
 package com.android.launcher3.ui;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.views.WorkFooterContainer;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+import java.util.Objects;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class WorkTabTest extends AbstractLauncherUiTest {
@@ -52,16 +67,50 @@
     }
 
     @Test
+    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void workTabExists() {
         mDevice.pressHome();
-        waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
 
-        /*
-        assertTrue("Personal tab is missing", waitForLauncherCondition(
-                launcher -> launcher.getAppsView().isPersonalTabVisible()));
-        assertTrue("Work tab is missing", waitForLauncherCondition(
-                launcher -> launcher.getAppsView().isWorkTabVisible()));
-        */
+        waitForLauncherCondition("Personal tab is missing",
+                launcher -> launcher.getAppsView().isPersonalTabVisible());
+        waitForLauncherCondition("Work tab is missing",
+                launcher -> launcher.getAppsView().isWorkTabVisible());
     }
+
+    @Test
+    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
+    public void toggleWorks() {
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
+        getOnceNotNull("Apps view did not bind",
+                launcher -> launcher.getAppsView().getWorkFooterContainer());
+
+        UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
+        assertEquals(2, userManager.getUserProfiles().size());
+        UserHandle workProfile = getFromLauncher(l -> {
+            UserHandle myHandle = Process.myUserHandle();
+            List<UserHandle> userProfiles = userManager.getUserProfiles();
+            return userProfiles.get(0) == myHandle ? userProfiles.get(1) : userProfiles.get(0);
+        });
+
+        waitForLauncherCondition("work profile can't be turned off",
+                l -> userManager.requestQuietModeEnabled(true, workProfile));
+
+        assertTrue(userManager.isQuietModeEnabled(workProfile));
+        executeOnLauncher(launcher -> {
+            WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer();
+            ((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately(
+                    AllAppsContainerView.AdapterHolder.WORK);
+            wf.getWorkModeSwitch().toggle();
+        });
+        waitForLauncherCondition("Work toggle did not work",
+                l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
+    }
+
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index abd0f24..c5fbd7c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1251,13 +1251,14 @@
     private Map<String, List<String>> getEvents() {
         final Map<String, List<String>> events = new HashMap<>();
         try {
-            // Logcat may skip events after the specified time. Querying for events starting 1 sec
+            // Logcat may skip events after the specified time. Querying for events starting 1 min
             // earlier.
-            final Date startTime = new Date(mStartRecordingTime.getTime() - 10000);
-            final String logcatEvents = mDevice.executeShellCommand(
-                    "logcat -d -v year --pid=" + getPid() + " -t "
-                            + DATE_TIME_FORMAT.format(startTime).replaceAll(" ", "")
-                            + " -s " + TestProtocol.TAPL_EVENTS_TAG);
+            final Date startTime = new Date(mStartRecordingTime.getTime() - 60000);
+            final String logcatCommand = "logcat -d -v year --pid=" + getPid() + " -t "
+                    + DATE_TIME_FORMAT.format(startTime).replaceAll(" ", "")
+                    + " -s " + TestProtocol.TAPL_EVENTS_TAG;
+            log("Events query command: " + logcatCommand);
+            final String logcatEvents = mDevice.executeShellCommand(logcatCommand);
             final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
             while (matcher.find()) {
                 // Skip events before recording start time.