Merge "Add biometric2 unit test owner"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ca184b1..8791c92 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -125,6 +125,7 @@
     <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
     <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
     <uses-permission android:name="android.permission.START_VIEW_APP_FEATURES" />
+    <uses-permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES" />
 
     <application
             android:name=".SettingsApplication"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f7d7e94..2dc4e65 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10627,6 +10627,14 @@
     <string name="lockscreen_double_line_clock_summary">Show double-line clock when available</string>
     <!-- Lockscreen double-line clock toggle [CHAR LIMIT=60] -->
     <string name="lockscreen_double_line_clock_setting_toggle">Double-line clock</string>
+    <!-- Lock screen buttons preference [CHAR LIMIT=60] -->
+    <string name="lockscreen_quick_affordances_title">Buttons</string>
+    <!-- Summary for the lock screen button preference [CHAR LIMIT=60] -->
+    <plurals name="lockscreen_quick_affordances_summary">
+        <item quantity="zero">None</item>
+        <item quantity="one"><xliff:g id="first">%1$s</xliff:g></item>
+        <item quantity="other"><xliff:g id="first">%1$s</xliff:g>, <xliff:g id="second">%2$s</xliff:g></item>
+    </plurals>
 
     <!-- Title for RTT setting. [CHAR LIMIT=NONE] -->
     <string name="rtt_settings_title"></string>
diff --git a/res/xml/security_lockscreen_settings.xml b/res/xml/security_lockscreen_settings.xml
index 3bd84f8..80e8fe6 100644
--- a/res/xml/security_lockscreen_settings.xml
+++ b/res/xml/security_lockscreen_settings.xml
@@ -69,6 +69,11 @@
             android:summary="@string/lockscreen_trivial_controls_summary"
             settings:controller="com.android.settings.display.ControlsTrivialPrivacyPreferenceController"/>
 
+        <Preference
+            android:key="customizable_lock_screen_quick_affordances"
+            android:title="@string/lockscreen_quick_affordances_title"
+            settings:controller="com.android.settings.display.CustomizableLockScreenQuickAffordancesPreferenceController" />
+
         <SwitchPreference
             android:key="lockscreen_double_line_clock_switch"
             android:title="@string/lockscreen_double_line_clock_setting_toggle"
diff --git a/src/com/android/settings/display/ControlsPrivacyPreferenceController.java b/src/com/android/settings/display/ControlsPrivacyPreferenceController.java
index 6f146d5..5b5b900 100644
--- a/src/com/android/settings/display/ControlsPrivacyPreferenceController.java
+++ b/src/com/android/settings/display/ControlsPrivacyPreferenceController.java
@@ -62,6 +62,11 @@
 
     @Override
     public int getAvailabilityStatus() {
+        // hide if we should use customizable lock screen quick affordances
+        if (CustomizableLockScreenUtils.isFeatureEnabled(mContext)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
         // hide if lockscreen isn't secure for this user
         return isEnabled() && isSecure() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
     }
diff --git a/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java b/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java
index 57f717b..7239ac7 100644
--- a/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java
+++ b/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java
@@ -70,6 +70,10 @@
 
     @Override
     public int getAvailabilityStatus() {
+        if (CustomizableLockScreenUtils.isFeatureEnabled(mContext)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
         return showDeviceControlsSettingsEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
     }
 
diff --git a/src/com/android/settings/display/CustomizableLockScreenQuickAffordancesPreferenceController.java b/src/com/android/settings/display/CustomizableLockScreenQuickAffordancesPreferenceController.java
new file mode 100644
index 0000000..94f1519
--- /dev/null
+++ b/src/com/android/settings/display/CustomizableLockScreenQuickAffordancesPreferenceController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+/**
+ * Preference for accessing an experience to customize lock screen quick affordances.
+ */
+public class CustomizableLockScreenQuickAffordancesPreferenceController extends
+        BasePreferenceController implements PreferenceControllerMixin {
+
+    public CustomizableLockScreenQuickAffordancesPreferenceController(Context context, String key) {
+        super(context, key);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return CustomizableLockScreenUtils.isFeatureEnabled(mContext)
+                ? AVAILABLE
+                : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final Preference preference = screen.findPreference(getPreferenceKey());
+        if (preference != null) {
+            preference.setOnPreferenceClickListener(preference1 -> {
+                // TODO(b/258471384): open the buttons destination within wallpaper picker.
+                final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+                final String packageName =
+                        mContext.getString(R.string.config_wallpaper_picker_package);
+                if (!TextUtils.isEmpty(packageName)) {
+                    intent.setPackage(packageName);
+                }
+                mContext.startActivity(intent);
+                return true;
+            });
+            refreshSummary(preference);
+        }
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return CustomizableLockScreenUtils.getQuickAffordanceSummary(mContext);
+    }
+}
diff --git a/src/com/android/settings/display/CustomizableLockScreenUtils.java b/src/com/android/settings/display/CustomizableLockScreenUtils.java
new file mode 100644
index 0000000..14601a3
--- /dev/null
+++ b/src/com/android/settings/display/CustomizableLockScreenUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Utilities for display settings related to customizable lock screen features. */
+public final class CustomizableLockScreenUtils {
+
+    private static final String TAG = "CustomizableLockScreenUtils";
+    private static final Uri BASE_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority("com.android.systemui.keyguard.quickaffordance")
+            .build();
+    @VisibleForTesting
+    static final Uri FLAGS_URI = BASE_URI.buildUpon()
+            .path("flags")
+            .build();
+    @VisibleForTesting
+    static final Uri SELECTIONS_URI = BASE_URI.buildUpon()
+            .path("selections")
+            .build();
+    @VisibleForTesting
+    static final String NAME = "name";
+    @VisibleForTesting
+    static final String VALUE = "value";
+    @VisibleForTesting
+    static final String ENABLED_FLAG = "is_feature_enabled";
+    @VisibleForTesting
+    static final String AFFORDANCE_NAME = "affordance_name";
+
+    private CustomizableLockScreenUtils() {}
+
+    /**
+     * Queries and returns whether the customizable lock screen quick affordances feature is enabled
+     * on the device.
+     *
+     * <p>This is a slow, blocking call that shouldn't be made on the main thread.
+     */
+    public static boolean isFeatureEnabled(Context context) {
+        try (Cursor cursor = context.getContentResolver().query(
+                FLAGS_URI,
+                null,
+                null,
+                null)) {
+            if (cursor == null) {
+                Log.w(TAG, "Cursor was null!");
+                return false;
+            }
+
+            final int indexOfNameColumn = cursor.getColumnIndex(NAME);
+            final int indexOfValueColumn = cursor.getColumnIndex(VALUE);
+            if (indexOfNameColumn == -1 || indexOfValueColumn == -1) {
+                Log.w(TAG, "Cursor doesn't contain " + NAME + " or " + VALUE + "!");
+                return false;
+            }
+
+            while (cursor.moveToNext()) {
+                final String name = cursor.getString(indexOfNameColumn);
+                final int value = cursor.getInt(indexOfValueColumn);
+                if (TextUtils.equals(ENABLED_FLAG, name)) {
+                    Log.d(TAG, ENABLED_FLAG + "=" + value);
+                    return value == 1;
+                }
+            }
+
+            Log.w(TAG, "Flag with name \"" + ENABLED_FLAG + "\" not found!");
+            return false;
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while querying quick affordance content provider", e);
+            return false;
+        }
+    }
+
+    /**
+     * Queries and returns a summary text for the currently-selected lock screen quick affordances.
+     *
+     * <p>This is a slow, blocking call that shouldn't be made on the main thread.
+     */
+    @Nullable
+    public static CharSequence getQuickAffordanceSummary(Context context) {
+        try (Cursor cursor = context.getContentResolver().query(
+                SELECTIONS_URI,
+                null,
+                null,
+                null)) {
+            if (cursor == null) {
+                Log.w(TAG, "Cursor was null!");
+                return null;
+            }
+
+            final int columnIndex = cursor.getColumnIndex(AFFORDANCE_NAME);
+            if (columnIndex == -1) {
+                Log.w(TAG, "Cursor doesn't contain \"" + AFFORDANCE_NAME + "\" column!");
+                return null;
+            }
+
+            final List<String> affordanceNames = new ArrayList<>(cursor.getCount());
+            while (cursor.moveToNext()) {
+                final String affordanceName = cursor.getString(columnIndex);
+                if (!TextUtils.isEmpty(affordanceName)) {
+                    affordanceNames.add(affordanceName);
+                }
+            }
+
+            // We don't display more than the first two items.
+            final int usableAffordanceNameCount = Math.min(2, affordanceNames.size());
+            final List<String> arguments = new ArrayList<>(usableAffordanceNameCount);
+            if (!affordanceNames.isEmpty()) {
+                arguments.add(affordanceNames.get(0));
+            }
+            if (affordanceNames.size() > 1) {
+                arguments.add(affordanceNames.get(1));
+            }
+
+            return context.getResources().getQuantityString(
+                    R.plurals.lockscreen_quick_affordances_summary,
+                    usableAffordanceNameCount,
+                    arguments.toArray());
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while querying quick affordance content provider", e);
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/display/QRCodeScannerPreferenceController.java b/src/com/android/settings/display/QRCodeScannerPreferenceController.java
index 16e594a..cb022a7 100644
--- a/src/com/android/settings/display/QRCodeScannerPreferenceController.java
+++ b/src/com/android/settings/display/QRCodeScannerPreferenceController.java
@@ -87,6 +87,10 @@
 
     @Override
     public int getAvailabilityStatus() {
+        if (CustomizableLockScreenUtils.isFeatureEnabled(mContext)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
         return isScannerActivityAvailable() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
diff --git a/src/com/android/settings/display/WalletPrivacyPreferenceController.java b/src/com/android/settings/display/WalletPrivacyPreferenceController.java
index 92580f3..fe14a40 100644
--- a/src/com/android/settings/display/WalletPrivacyPreferenceController.java
+++ b/src/com/android/settings/display/WalletPrivacyPreferenceController.java
@@ -62,6 +62,10 @@
 
     @Override
     public int getAvailabilityStatus() {
+        if (CustomizableLockScreenUtils.isFeatureEnabled(mContext)) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
         return isEnabled() && isSecure() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
     }
 
diff --git a/tests/robotests/src/com/android/settings/display/CustomizableLockScreenQuickAffordancesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/CustomizableLockScreenQuickAffordancesPreferenceControllerTest.java
new file mode 100644
index 0000000..8597d64
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/CustomizableLockScreenQuickAffordancesPreferenceControllerTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+public class CustomizableLockScreenQuickAffordancesPreferenceControllerTest {
+
+    private static final String KEY = "key";
+
+    @Mock private Context mContext;
+    @Mock private ContentResolver mContentResolver;
+
+    private CustomizableLockScreenQuickAffordancesPreferenceController mUnderTest;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        when(mContext.getResources())
+                .thenReturn(ApplicationProvider.getApplicationContext().getResources());
+
+        mUnderTest = new CustomizableLockScreenQuickAffordancesPreferenceController(mContext, KEY);
+    }
+
+    @Test
+    public void getAvailabilityStatus_whenEnabled() {
+        setEnabled(true);
+
+        assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_whenNotEnabled() {
+        setEnabled(false);
+
+        assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void displayPreference_click() {
+        setSelectedAffordanceNames("one", "two");
+        final PreferenceScreen screen = mock(PreferenceScreen.class);
+        final Preference preference = mock(Preference.class);
+        when(screen.findPreference(KEY)).thenReturn(preference);
+
+        mUnderTest.displayPreference(screen);
+
+        final ArgumentCaptor<Preference.OnPreferenceClickListener> clickCaptor =
+                ArgumentCaptor.forClass(Preference.OnPreferenceClickListener.class);
+        verify(preference).setOnPreferenceClickListener(clickCaptor.capture());
+
+        clickCaptor.getValue().onPreferenceClick(preference);
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(preference).setOnPreferenceClickListener(clickCaptor.capture());
+        verify(mContext).startActivity(intentCaptor.capture());
+        assertThat(intentCaptor.getValue().getPackage()).isEqualTo(
+                mContext.getString(R.string.config_wallpaper_picker_package));
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(Intent.ACTION_SET_WALLPAPER);
+    }
+
+    @Test
+    public void getSummary_whenNoneAreSelected() {
+        setSelectedAffordanceNames();
+
+        assertThat(mUnderTest.getSummary()).isNull();
+    }
+
+    @Test
+    public void getSummary_whenOneIsSelected() {
+        setSelectedAffordanceNames("one");
+
+        assertThat(TextUtils.equals(mUnderTest.getSummary(), "one")).isTrue();
+    }
+
+    @Test
+    public void getSummary_whenTwoAreSelected() {
+        setSelectedAffordanceNames("one", "two");
+
+        assertThat(TextUtils.equals(mUnderTest.getSummary(), "one, two")).isTrue();
+    }
+
+    private void setEnabled(boolean isEnabled) {
+        final MatrixCursor cursor = new MatrixCursor(
+                new String[] {
+                        CustomizableLockScreenUtils.NAME,
+                        CustomizableLockScreenUtils.VALUE
+                });
+        cursor.addRow(new Object[] { CustomizableLockScreenUtils.ENABLED_FLAG, isEnabled ? 1 : 0 });
+        when(
+                mContentResolver.query(
+                        CustomizableLockScreenUtils.FLAGS_URI, null, null, null))
+                .thenReturn(cursor);
+    }
+
+    private void setSelectedAffordanceNames(String... affordanceNames) {
+        final MatrixCursor cursor = new MatrixCursor(
+                new String[] { CustomizableLockScreenUtils.AFFORDANCE_NAME });
+        for (final String name : affordanceNames) {
+            cursor.addRow(new Object[] { name });
+        }
+
+        when(
+                mContentResolver.query(
+                        CustomizableLockScreenUtils.SELECTIONS_URI, null, null, null))
+                .thenReturn(cursor);
+    }
+}
diff --git a/tests/spa_unit/Android.bp b/tests/spa_unit/Android.bp
index da33411..0bd5613 100644
--- a/tests/spa_unit/Android.bp
+++ b/tests/spa_unit/Android.bp
@@ -30,13 +30,11 @@
 
     static_libs: [
         "Settings-core",
+        "SpaLibTestUtils",
         "androidx.compose.runtime_runtime",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-extended-minus-junit4",
-        "truth-prebuilt",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index d71b4ee..0ca4f67 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -31,8 +31,8 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.settings.testutils.delay
 import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spa.testutils.delay
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 import org.junit.After
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt
index 174f508..b6816e6 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.printToLog
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -39,9 +38,9 @@
 import com.android.settings.Utils
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
 import com.android.settings.datausage.AppDataUsage
-import com.android.settings.testutils.waitUntilExists
 import com.android.settingslib.net.NetworkCycleDataForUid
 import com.android.settingslib.net.NetworkCycleDataForUidLoader
+import com.android.settingslib.spa.testutils.waitUntilExists
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -134,9 +133,7 @@
 
         setContent()
 
-        composeTestRule.onRoot().printToLog("AAA")
-        composeTestRule.onNodeWithText(context.getString(R.string.no_data_usage))
-            .assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(context.getString(R.string.no_data_usage)))
     }
 
     @Test
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt
index f35810f..beb6abc 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt
@@ -61,7 +61,7 @@
     private lateinit var devicePolicyManager: DevicePolicyManager
 
     private val fakeFeatureFactory = FakeFeatureFactory()
-    private val appFeatureProvider = fakeFeatureFactory.applicationFeatureProvider
+    private val appFeatureProvider = fakeFeatureFactory.mockApplicationFeatureProvider
 
     private lateinit var appDisableButton: AppDisableButton
 
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
index b66967a..53a1868 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
@@ -38,8 +38,8 @@
 import com.android.settings.R
 import com.android.settings.Utils
 import com.android.settings.applications.AppStoreUtil
-import com.android.settings.testutils.waitUntilExists
 import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spa.testutils.waitUntilExists
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import org.junit.After
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
index 1184ee7..a1fb367 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
@@ -29,18 +29,13 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
-import androidx.compose.ui.test.assertIsNotEnabled
-import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.printToLog
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
-import com.android.settings.testutils.waitUntilExists
-import com.android.settingslib.applications.AppUtils
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.google.common.truth.Truth.assertThat
@@ -49,7 +44,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
index e3fcdd9..b2ff4f2 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
@@ -63,7 +63,7 @@
     private lateinit var packageManager: PackageManager
 
     private val fakeFeatureFactory = FakeFeatureFactory()
-    private val appFeatureProvider = fakeFeatureFactory.applicationFeatureProvider
+    private val appFeatureProvider = fakeFeatureFactory.mockApplicationFeatureProvider
 
     @Before
     fun setUp() {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
index 9782817..1a78c49 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
@@ -24,10 +24,6 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
-import androidx.compose.ui.test.filterToOne
-import androidx.compose.ui.test.hasAnyAncestor
-import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -37,7 +33,8 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.settings.R
 import com.android.settings.Utils
-import com.android.settings.testutils.delay
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spa.testutils.onDialogText
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -141,18 +138,14 @@
         composeTestRule.onRoot().performClick()
         composeTestRule.delay()
 
-        assertDialogHasText(context.getString(R.string.app_launch_supported_domain_urls_title))
-        assertDialogHasText("abc")
-        assertDialogHasText("def")
+        composeTestRule.onDialogText(
+            context.getString(R.string.app_launch_supported_domain_urls_title)
+        ).assertIsDisplayed()
+        composeTestRule.onDialogText("abc").assertIsDisplayed()
+        composeTestRule.onDialogText("def").assertIsDisplayed()
     }
 
-    private fun assertDialogHasText(text: String) {
-        composeTestRule.onAllNodes(hasAnyAncestor(isDialog()))
-            .filterToOne(hasText(text))
-            .assertIsDisplayed()
-    }
-
-    private fun setContent(app:ApplicationInfo = INSTANT_APP) {
+    private fun setContent(app: ApplicationInfo = INSTANT_APP) {
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
                 InstantAppDomainsPreference(app)
diff --git a/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt b/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt
deleted file mode 100644
index 82df9cf..0000000
--- a/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.testutils
-
-import androidx.compose.ui.test.ComposeTimeoutException
-import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-
-/** Blocks until the found a semantics node that match the given condition. */
-fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
-    onAllNodes(matcher).fetchSemanticsNodes().isNotEmpty()
-}
-
-/** Blocks until the timeout is reached. */
-fun ComposeContentTestRule.delay(timeoutMillis: Long = 1_000) = try {
-    waitUntil(timeoutMillis) { false }
-} catch (_: ComposeTimeoutException) {
-    // Expected
-}
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 054b415..a544f53 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -51,7 +51,9 @@
 
 class FakeFeatureFactory : FeatureFactory() {
 
-    val applicationFeatureProvider: ApplicationFeatureProvider =
+    private val mockMetricsFeatureProvider: MetricsFeatureProvider =
+        mock(MetricsFeatureProvider::class.java)
+    val mockApplicationFeatureProvider: ApplicationFeatureProvider =
         mock(ApplicationFeatureProvider::class.java)
 
     init {
@@ -70,9 +72,7 @@
         TODO("Not yet implemented")
     }
 
-    override fun getMetricsFeatureProvider(): MetricsFeatureProvider {
-        TODO("Not yet implemented")
-    }
+    override fun getMetricsFeatureProvider(): MetricsFeatureProvider = mockMetricsFeatureProvider
 
     override fun getPowerUsageFeatureProvider(context: Context?): PowerUsageFeatureProvider {
         TODO("Not yet implemented")
@@ -96,7 +96,7 @@
         TODO("Not yet implemented")
     }
 
-    override fun getApplicationFeatureProvider(context: Context?) = applicationFeatureProvider
+    override fun getApplicationFeatureProvider(context: Context?) = mockApplicationFeatureProvider
 
     override fun getLocaleFeatureProvider(): LocaleFeatureProvider {
         TODO("Not yet implemented")