Merge "Improve null safety for app pairs" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 210cfd0..228c34a 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -67,7 +67,7 @@
     name: "enable_taskbar_pinning"
     namespace: "launcher"
     description: "Enables taskbar pinning to allow user to switch between transient and persistent taskbar flavors."
-    bug: "270396583"
+    bug: "296231746"
 }
 
 flag {
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 232c441..2a1f39f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -322,7 +322,6 @@
 
     <!-- Taskbar -->
     <dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
-    <dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
     <dimen name="taskbar_ime_size">48dp</dimen>
     <dimen name="taskbar_icon_min_touch_size">48dp</dimen>
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 7583cc1..a96ee1f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -22,10 +22,8 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
-import androidx.core.view.children
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
 import com.android.systemui.shared.rotation.RotationButton
 
 open class PhoneLandscapeNavLayoutter(
@@ -48,43 +46,62 @@
     ) {
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
-        // TODO(b/230395757): Polish pending, this is just to make it usable
-        val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
-        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile,
-                resources, context.isPhoneMode)
-        navButtonContainer.removeAllViews()
-        navButtonContainer.orientation = LinearLayout.VERTICAL
+        val totalHeight = context.deviceProfile.heightPx
+        val homeButtonHeight = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_home_button_size)
+        val roundedCornerContentMargin = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_rounded_corner_content_margin)
+        val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+        val contentWidth = totalHeight - roundedCornerContentMargin * 2 - contentPadding * 2
+
+        // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+        val contextualButtonHeight = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+        val sideButtonHeight = contextualButtonHeight * 2
+        val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
 
         val navContainerParams = FrameLayout.LayoutParams(
-                taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
+            ViewGroup.LayoutParams.MATCH_PARENT, navButtonContainerHeight.toInt())
         navContainerParams.apply {
-            topMargin = endStartMargins
-            bottomMargin = endStartMargins
+            topMargin =
+                    (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+            bottomMargin =
+                    (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
             marginEnd = 0
             marginStart = 0
         }
 
-        navButtonContainer.layoutParams = navContainerParams
-        navButtonContainer.gravity = Gravity.CENTER
+        // Ensure order of buttons is correct
+        navButtonContainer.removeAllViews()
+        navButtonContainer.orientation = LinearLayout.VERTICAL
 
         addThreeButtons()
 
+        navButtonContainer.layoutParams = navContainerParams
+        navButtonContainer.gravity = Gravity.CENTER
+
         // Add the spaces in between the nav buttons
-        val spaceInBetween: Int =
-            resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
-        navButtonContainer.children.forEachIndexed { i, navButton ->
+        val spaceInBetween = (navButtonContainerHeight - homeButtonHeight -
+                sideButtonHeight * 2) / 2.0f
+        for (i in 0 until navButtonContainer.childCount) {
+            val navButton = navButtonContainer.getChildAt(i)
             val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
-            buttonLayoutParams.weight = 1f
+            val margin = (spaceInBetween / 2).toInt()
             when (i) {
                 0 -> {
-                    buttonLayoutParams.bottomMargin = spaceInBetween / 2
+                    // First button
+                    buttonLayoutParams.bottomMargin = margin
+                    buttonLayoutParams.height = sideButtonHeight.toInt()
                 }
                 navButtonContainer.childCount - 1 -> {
-                    buttonLayoutParams.topMargin = spaceInBetween / 2
+                    // Last button
+                    buttonLayoutParams.topMargin = margin
+                    buttonLayoutParams.height = sideButtonHeight.toInt()
                 }
                 else -> {
-                    buttonLayoutParams.bottomMargin = spaceInBetween / 2
-                    buttonLayoutParams.topMargin = spaceInBetween / 2
+                    // other buttons
+                    buttonLayoutParams.topMargin = margin
+                    buttonLayoutParams.bottomMargin = margin
+                    buttonLayoutParams.height = homeButtonHeight
                 }
             }
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 4388ce6..22d9f82 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -24,7 +24,6 @@
 import android.widget.LinearLayout
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
 import com.android.systemui.shared.rotation.RotationButton
 
 class PhonePortraitNavLayoutter(
@@ -47,26 +46,33 @@
     ) {
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
-        // TODO(b/230395757): Polish pending, this is just to make it usable
-        val taskbarDimensions =
-            DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile, resources,
-                    context.isPhoneMode)
-        val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+        val totalWidth = context.deviceProfile.widthPx
+        val homeButtonWidth = resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
+        val roundedCornerContentMargin = resources.getDimensionPixelSize(
+                R.dimen.taskbar_phone_rounded_corner_content_margin)
+        val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+        val contentWidth = totalWidth - roundedCornerContentMargin * 2 - contentPadding * 2
+
+        // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+        val contextualButtonWidth = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+        val sideButtonWidth = contextualButtonWidth * 2
+        val navButtonContainerWidth = contentWidth - contextualButtonWidth * 2
+
+        val navContainerParams = FrameLayout.LayoutParams(navButtonContainerWidth.toInt(),
+                ViewGroup.LayoutParams.MATCH_PARENT)
+        navContainerParams.apply {
+            topMargin = 0
+            bottomMargin = 0
+            marginEnd =
+                    (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+            marginStart =
+                    (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+        }
 
         // Ensure order of buttons is correct
         navButtonContainer.removeAllViews()
         navButtonContainer.orientation = LinearLayout.HORIZONTAL
 
-        val navContainerParams = FrameLayout.LayoutParams(
-                taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
-        navContainerParams.apply {
-            topMargin = 0
-            bottomMargin = 0
-            marginEnd = endStartMargins
-            marginStart = endStartMargins
-        }
-
-        // Swap recents and back button in case we were landscape prior to this
         navButtonContainer.addView(backButton)
         navButtonContainer.addView(homeButton)
         navButtonContainer.addView(recentsButton)
@@ -75,25 +81,28 @@
         navButtonContainer.gravity = Gravity.CENTER
 
         // Add the spaces in between the nav buttons
-        val spaceInBetween =
-            resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+        val spaceInBetween = (navButtonContainerWidth - homeButtonWidth -
+                sideButtonWidth * 2) / 2.0f
         for (i in 0 until navButtonContainer.childCount) {
             val navButton = navButtonContainer.getChildAt(i)
             val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
-            buttonLayoutParams.weight = 1f
+            val margin = (spaceInBetween / 2).toInt()
             when (i) {
                 0 -> {
                     // First button
-                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                    buttonLayoutParams.marginEnd = margin
+                    buttonLayoutParams.width = sideButtonWidth.toInt()
                 }
                 navButtonContainer.childCount - 1 -> {
                     // Last button
-                    buttonLayoutParams.marginStart = spaceInBetween / 2
+                    buttonLayoutParams.marginStart = margin
+                    buttonLayoutParams.width = sideButtonWidth.toInt()
                 }
                 else -> {
                     // other buttons
-                    buttonLayoutParams.marginStart = spaceInBetween / 2
-                    buttonLayoutParams.marginEnd = spaceInBetween / 2
+                    buttonLayoutParams.marginStart = margin
+                    buttonLayoutParams.marginEnd = margin
+                    buttonLayoutParams.width = homeButtonWidth
                 }
             }
         }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 8e03a91..69db91b 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -206,8 +206,9 @@
                     }
                 }
 
-                RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx)
-                        .onStartingSplitLegacy(appearedTaskTargets);
+                RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
+                        ? null : SystemUiProxy.INSTANCE.get(mCtx).onStartingSplitLegacy(
+                                appearedTaskTargets);
                 if (nonAppTargets == null) {
                     nonAppTargets = new RemoteAnimationTarget[0];
                 }
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0ebcbf3..6d115b2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -56,7 +56,7 @@
     <!-- App Widget resize frame -->
     <dimen name="widget_handle_margin">13dp</dimen>
     <dimen name="resize_frame_background_padding">24dp</dimen>
-    <dimen name="resize_frame_margin">23dp</dimen>
+    <dimen name="resize_frame_margin">22dp</dimen>
     <dimen name="resize_frame_invalid_drag_across_two_panel_opacity_margin">24dp</dimen>
 
     <!-- App widget reconfigure button -->
@@ -369,6 +369,11 @@
     <!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
     <dimen name="taskbar_size">0dp</dimen>
     <dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
+    <dimen name="taskbar_phone_home_button_size">80dp</dimen>
+    <dimen name="taskbar_phone_content_padding">8dp</dimen>
+    <dimen name="taskbar_phone_rounded_corner_content_margin">
+        @*android:dimen/rounded_corner_content_padding
+    </dimen>
     <dimen name="taskbar_stashed_size">0dp</dimen>
     <dimen name="qsb_widget_height">0dp</dimen>
     <dimen name="qsb_shadow_height">0dp</dimen>
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f9d282c..aa6d1f2 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -305,7 +305,7 @@
 
     // TODO(Block 17): Clean up flags
     // Aconfig migration complete for ENABLE_TASKBAR_PINNING.
-    private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
+    private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746,
             "ENABLE_TASKBAR_PINNING", TEAMFOOD,
             "Enables taskbar pinning to allow user to switch between transient and persistent "
                     + "taskbar flavors");
@@ -476,10 +476,10 @@
 
     // TODO(Block 33): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
-            "ENABLE_ALL_APPS_RV_PREINFLATION", TEAMFOOD,
+            "ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
             "Enables preinflating all apps icons to avoid scrolling jank.");
     public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
-            "ALL_APPS_GONE_VISIBILITY", TEAMFOOD,
+            "ALL_APPS_GONE_VISIBILITY", ENABLED,
             "Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
 
     // TODO(Block 34): Empty block
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 20b2971..fbe877d 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -91,7 +91,8 @@
 
     public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
     public static final String APPWIDGET_IDS = "appwidget_ids";
-    private static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
+    @VisibleForTesting
+    public static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
             "container", "cellX", "cellY", "spanX", "spanY", "intent", "appWidgetProvider",
             "appWidgetId", "restored"};
 
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
index 4a1888a..b6ded97 100644
--- a/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
+++ b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
@@ -23,16 +23,43 @@
 public class TaplUtilityTest {
 
     @Test
-    public void testNewStringWithRegex() {
+    public void testMakeMultilinePattern() {
+        // Original title will match.
         assertTrue(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("Play Store has 7 notifications").matches());
+                .matcher("Play Store").matches());
+        assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+                .matcher("PlayStore").matches());
+
+        // Original title with whitespace added will match.
+        assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+                .matcher("Play\nStore").matches());
+        assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+                .matcher("Play Store").matches());
+        // Original title with whitespace removed will also match.
         assertTrue(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("Play  Store!").matches());
-        assertFalse(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("play  store").matches());
-        assertFalse(AppIcon.makeMultilinePattern("Play Store")
-                .matcher("").matches());
+                .matcher("PlayStore").matches());
+        // Or whitespace replaced with a different kind of whitespace (both of above conditions).
+        assertTrue(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play\nStore").matches());
         assertTrue(AppIcon.makeMultilinePattern("Play Store")
                 .matcher("Play \n Store").matches());
+
+        // Any non-whitespace character added to the title will not match.
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play Store has 7 notifications").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play  Store!").matches());
+        // Title is case-sensitive.
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("play store").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("play  store").matches());
+        // Removing non whitespace characters will not match.
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play Stor").matches());
+        assertFalse(AppIcon.makeMultilinePattern("Play Store")
+                .matcher("Play").matches());
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index f818564..5d41da0 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
 
 import static org.junit.Assert.assertEquals;
@@ -45,6 +46,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.Objects;
 import java.util.function.Predicate;
 
@@ -98,7 +100,17 @@
             launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
         });
         TestUtil.uninstallDummyApp();
-        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+
+        mLauncher.runToState(
+                () -> {
+                    try {
+                        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
+                NORMAL_STATE_ORDINAL,
+                "executing pm 'remove-user' command");
     }
 
     private void waitForWorkTabSetup() {
diff --git a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
index 6d00253..972b592 100644
--- a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
+++ b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
@@ -63,8 +63,8 @@
     fun run_and_complete() {
         awaitAllExecutorCompleted()
 
-        assertTrue(isTaskExecuted)
-        assertTrue(isCallbackExecuted)
+        assertTrue("task should be executed", isTaskExecuted)
+        assertTrue("callback should be executed", isCallbackExecuted)
         assertEquals(2, result)
     }
 
@@ -76,7 +76,7 @@
         underTest.cancel(false)
         awaitAllExecutorCompleted()
 
-        assertFalse(isCallbackExecuted)
+        assertFalse("callback should not be executed.", isCallbackExecuted)
         assertEquals(0, result)
     }
 
@@ -86,8 +86,8 @@
 
         underTest.cancel(false)
 
-        assertTrue(isTaskExecuted)
-        assertTrue(isCallbackExecuted)
+        assertTrue("task should be executed", isTaskExecuted)
+        assertTrue("callback should be executed", isCallbackExecuted)
         assertEquals(2, result)
     }
 
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 61ec669..9d9bd517 100644
--- a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,7 +1,27 @@
 package com.android.launcher3.util
 
+import android.content.ContentValues
 import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE
+import com.android.launcher3.LauncherSettings.Favorites.CELLX
+import com.android.launcher3.LauncherSettings.Favorites.CELLY
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.INTENT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID
+import com.android.launcher3.LauncherSettings.Favorites.RESTORED
+import com.android.launcher3.LauncherSettings.Favorites.SCREEN
+import com.android.launcher3.LauncherSettings.Favorites.SPANX
+import com.android.launcher3.LauncherSettings.Favorites.SPANY
+import com.android.launcher3.LauncherSettings.Favorites.TITLE
+import com.android.launcher3.LauncherSettings.Favorites._ID
 import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.ModelDbController
 
 object ModelTestExtensions {
     /** Clears and reloads Launcher db to cleanup the workspace */
@@ -20,6 +40,7 @@
         loadModelSync()
     }
 
+    /** Loads the model in memory synchronously */
     fun LauncherModel.loadModelSync() {
         val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
         TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) }
@@ -27,4 +48,54 @@
         TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
         TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) }
     }
+
+    /** Adds and commits a new item to Launcher.db */
+    fun LauncherModel.addItem(
+        title: String = "LauncherTestApp",
+        intent: String =
+            "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;component=com.google.android.apps.nexuslauncher.tests/com.android.launcher3.testcomponent.BaseTestingActivity;launchFlags=0x10200000;end",
+        type: Int = ITEM_TYPE_APPLICATION,
+        restoreFlags: Int = 0,
+        screen: Int = 0,
+        container: Int = CONTAINER_DESKTOP,
+        x: Int,
+        y: Int,
+        spanX: Int = 1,
+        spanY: Int = 1,
+        id: Int = 0,
+        profileId: Int = 0,
+        tableName: String = Favorites.TABLE_NAME,
+        appWidgetId: Int = -1,
+        appWidgetSource: Int = -1,
+        appWidgetProvider: String? = null
+    ) {
+        loadModelSync()
+        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+            val controller: ModelDbController = modelDbController
+            controller.tryMigrateDB()
+            modelDbController.newTransaction().use { transaction ->
+                val values =
+                    ContentValues().apply {
+                        values[_ID] = id
+                        values[TITLE] = title
+                        values[PROFILE_ID] = profileId
+                        values[CONTAINER] = container
+                        values[SCREEN] = screen
+                        values[CELLX] = x
+                        values[CELLY] = y
+                        values[SPANX] = spanX
+                        values[SPANY] = spanY
+                        values[ITEM_TYPE] = type
+                        values[RESTORED] = restoreFlags
+                        values[INTENT] = intent
+                        values[APPWIDGET_ID] = appWidgetId
+                        values[APPWIDGET_SOURCE] = appWidgetSource
+                        values[APPWIDGET_PROVIDER] = appWidgetProvider
+                    }
+                // Migrate any previous data so that the DB state is correct
+                controller.insert(tableName, values)
+                transaction.commit()
+            }
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 85098c8..867a1a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -36,9 +36,23 @@
         super(launcher, icon);
     }
 
+    /**
+     * Find an app icon with the given name.
+     *
+     * @param appName app icon to look for
+     */
+    static BySelector getAppIconSelector(String appName) {
+        return By.clazz(TextView.class).text(makeMultilinePattern(appName));
+    }
+
+    /**
+     * Find an app icon with the given name.
+     *
+     * @param appName app icon to look for
+     * @param launcher (optional) - only match ui elements from Launcher's package
+     */
     static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
-        return By.clazz(TextView.class).desc(makeMultilinePattern(appName))
-                .pkg(launcher.getLauncherPackageName());
+        return getAppIconSelector(appName).pkg(launcher.getLauncherPackageName());
     }
 
     static BySelector getMenuItemSelector(String text, LauncherInstrumentation launcher) {
@@ -109,13 +123,16 @@
     }
 
     /**
-     * Create a regular expression pattern that matches strings starting with the app name, where
-     * spaces in the app name are replaced with zero or more occurrences of the "\s" character
-     * (which represents a whitespace character in regular expressions), followed by any characters
-     * after the app name.
+     * Create a regular expression pattern that matches strings containing all of the non-whitespace
+     * characters of the app name, with any amount of whitespace added between characters (e.g.
+     * newline for multiline app labels).
      */
     static Pattern makeMultilinePattern(String appName) {
-        return Pattern.compile(appName.replaceAll("\\s+", "\\\\s*") + ".*",
-                Pattern.DOTALL);
+        // Remove any existing whitespace.
+        appName = appName.replaceAll("\\s", "");
+        // Allow whitespace between characters, e.g. newline for 2 line app label.
+        StringBuilder regexBuldier = new StringBuilder("\\s*");
+        appName.chars().forEach(letter -> regexBuldier.append((char) letter).append("\\s*"));
+        return Pattern.compile(regexBuldier.toString());
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index b68fc4e..28e2590 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -62,7 +62,13 @@
                         + mObject.getVisibleCenter() + " in "
                         + mLauncher.getVisibleBounds(mObject));
 
-                performClick();
+                if (launcherStopsAfterLaunch()) {
+                    mLauncher.executeAndWaitForLauncherStop(
+                            () -> mLauncher.clickLauncherObject(mObject),
+                            "clicking the launchable");
+                } else {
+                    mLauncher.clickLauncherObject(mObject);
+                }
 
                 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
                     expectActivityStartEvents();
@@ -72,16 +78,6 @@
         }
     }
 
-    private void performClick() {
-        if (launcherStopsAfterLaunch()) {
-            mLauncher.executeAndWaitForLauncherStop(
-                    () -> mLauncher.clickLauncherObject(mObject),
-                    "clicking the launchable");
-        } else {
-            mLauncher.clickLauncherObject(mObject);
-        }
-    }
-
     protected abstract void expectActivityStartEvents();
 
     protected abstract String launchableType();
@@ -100,7 +96,9 @@
                     + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(
                     mObject));
 
-            performClick();
+            mLauncher.executeAndWaitForLauncherStop(
+                    () -> mLauncher.clickLauncherObject(mObject),
+                    "clicking the launchable");
 
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index edc6aac..2896ede 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1529,7 +1529,8 @@
         }
     }
 
-    void runToState(Runnable command, int expectedState, String actionName) {
+    /** Run an action and wait for the specified Launcher state. */
+    public void runToState(Runnable command, int expectedState, String actionName) {
         final List<Integer> actualEvents = new ArrayList<>();
         executeAndWaitForLauncherEvent(
                 command,
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index f0a8aa2..963bf79 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -39,7 +39,7 @@
 
     /** Find the app from search results with app name. */
     public AppIcon findAppIcon(String appName) {
-        UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
+        UiObject2 icon = mLauncher.waitForLauncherObject(AppIcon.getAppIconSelector(appName));
         return createAppIcon(icon);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
index 064f80c..d05c112 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
@@ -68,6 +68,7 @@
 
     @Override
     protected boolean launcherStopsAfterLaunch() {
+        // false because if taskbar is showing then launcher is already stopped.
         return false;
     }
 }