Let SpaBridgeActivity support ActivityEmbedding

Move the multi pane shared logic into EmbeddedDeepLinkUtils.

Fix: 309075424
Test: manual - with SpaActivity
Test: unit tests
Test: m RunSettingsRoboTests ROBOTEST_FILTER=".*\.SettingsActivityTest"
Change-Id: I8c41c801b8a5009a3959c85b784ed9739d947a70
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index bc061e3..4c20231 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -16,16 +16,12 @@
 
 package com.android.settings;
 
-import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY;
-import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY;
-import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI;
-
+import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink;
 import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;
 
 import android.app.ActionBar;
 import android.app.ActivityManager;
 import android.app.settings.SettingsEnums;
-import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,7 +31,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.graphics.drawable.Icon;
@@ -67,7 +62,6 @@
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.core.gateway.SettingsGateway;
 import com.android.settings.dashboard.DashboardFeatureProvider;
-import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
 import com.android.settings.homepage.SettingsHomepageActivity;
 import com.android.settings.homepage.TopLevelSettings;
 import com.android.settings.overlay.FeatureFactory;
@@ -278,7 +272,8 @@
         getMetaData();
         final Intent intent = getIntent();
 
-        if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) {
+        if (shouldShowMultiPaneDeepLink(intent)
+                && tryStartMultiPaneDeepLink(this, intent, mHighlightMenuKey)) {
             finish();
             super.onCreate(savedState);
             return;
@@ -415,73 +410,7 @@
             intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
     }
 
-    /**
-     * Returns the deep link trampoline intent for large screen devices.
-     */
-    public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) {
-        final Intent detailIntent = new Intent(intent);
-        // Guard against the arbitrary Intent injection.
-        if (detailIntent.getSelector() != null) {
-            detailIntent.setSelector(null);
-        }
-        // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it.
-        final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)
-                .setPackage(Utils.SETTINGS_PACKAGE_NAME)
-                .replaceExtras(detailIntent);
-
-        // Relay detail intent data to prevent failure of Intent#ParseUri.
-        // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme of
-        // Intent#getData() and it may not be the scheme of an Intent.
-        trampolineIntent.putExtra(
-                SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
-                detailIntent.getData());
-        detailIntent.setData(null);
-
-        trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
-                detailIntent.toUri(Intent.URI_INTENT_SCHEME));
-
-        trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
-                highlightMenuKey);
-        trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
-        return trampolineIntent;
-    }
-
-    private boolean tryStartTwoPaneDeepLink(Intent intent) {
-        intent.putExtra(EXTRA_INITIAL_CALLING_PACKAGE, PasswordUtils.getCallingAppPackageName(
-                getActivityToken()));
-        final Intent trampolineIntent;
-        if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) {
-            // Get menu key for slice deep link case.
-            final String highlightMenuKey = intent.getStringExtra(
-                    EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
-            if (!TextUtils.isEmpty(highlightMenuKey)) {
-                mHighlightMenuKey = highlightMenuKey;
-            }
-            trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
-            trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal.class);
-        } else {
-            trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey);
-        }
-
-        try {
-            final UserManager um = getSystemService(UserManager.class);
-            final UserInfo userInfo = um.getUserInfo(getUser().getIdentifier());
-            if (userInfo.isManagedProfile()) {
-                trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal.class)
-                        .putExtra(EXTRA_USER_HANDLE, getUser());
-                startActivityAsUser(trampolineIntent,
-                        um.getProfileParent(userInfo.id).getUserHandle());
-            } else {
-                startActivity(trampolineIntent);
-            }
-        } catch (ActivityNotFoundException e) {
-            Log.e(LOG_TAG, "Deep link homepage is not available to show 2-pane UI");
-            return false;
-        }
-        return true;
-    }
-
-    private boolean shouldShowTwoPaneDeepLink(Intent intent) {
+    private boolean shouldShowMultiPaneDeepLink(Intent intent) {
         if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
             return false;
         }
diff --git a/src/com/android/settings/SettingsActivityUtil.kt b/src/com/android/settings/SettingsActivityUtil.kt
index c23bc18..4238ff8 100644
--- a/src/com/android/settings/SettingsActivityUtil.kt
+++ b/src/com/android/settings/SettingsActivityUtil.kt
@@ -28,7 +28,7 @@
 import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails
 import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings
 import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
-import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
+import com.android.settings.spa.SpaAppBridgeActivity.Companion.getDestinationForApp
 import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
 import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
 import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
@@ -72,17 +72,18 @@
 
     @JvmStatic
     fun Context.launchSpaActivity(fragmentName: String, intent: Intent): Boolean {
-        if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
-            return false
-        }
-        FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]?.let { destination ->
-            startSpaActivity(destination)
-            return true
-        }
-        FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { appDestinationPrefix ->
-            startSpaActivityForApp(appDestinationPrefix, intent)
-            return true
+        if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
+            getDestination(fragmentName, intent)?.let { destination ->
+                startSpaActivity(destination)
+                return true
+            }
         }
         return false
     }
+
+    private fun getDestination(fragmentName: String, intent: Intent): String? =
+        FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]
+            ?: FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { destinationPrefix ->
+                getDestinationForApp(destinationPrefix, intent)
+            }
 }
diff --git a/src/com/android/settings/activityembedding/EmbeddedDeepLinkUtils.kt b/src/com/android/settings/activityembedding/EmbeddedDeepLinkUtils.kt
new file mode 100644
index 0000000..2bc8cda
--- /dev/null
+++ b/src/com/android/settings/activityembedding/EmbeddedDeepLinkUtils.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.activityembedding
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.util.Log
+import com.android.settings.SettingsActivity
+import com.android.settings.Utils
+import com.android.settings.homepage.DeepLinkHomepageActivityInternal
+import com.android.settings.homepage.SettingsHomepageActivity
+import com.android.settings.password.PasswordUtils
+import com.android.settingslib.spaprivileged.framework.common.userManager
+
+object EmbeddedDeepLinkUtils {
+    private const val TAG = "EmbeddedDeepLinkUtils"
+
+    @JvmStatic
+    fun Activity.tryStartMultiPaneDeepLink(
+        intent: Intent,
+        highlightMenuKey: String? = null,
+    ): Boolean {
+        intent.putExtra(
+            SettingsActivity.EXTRA_INITIAL_CALLING_PACKAGE,
+            PasswordUtils.getCallingAppPackageName(activityToken),
+        )
+        val trampolineIntent: Intent
+        if (intent.getBooleanExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false)) {
+            // Get menu key for slice deep link case.
+            var sliceHighlightMenuKey: String? = intent.getStringExtra(
+                Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY
+            )
+            if (sliceHighlightMenuKey.isNullOrEmpty()) {
+                sliceHighlightMenuKey = highlightMenuKey
+            }
+            trampolineIntent = getTrampolineIntent(intent, sliceHighlightMenuKey)
+            trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal::class.java)
+        } else {
+            trampolineIntent = getTrampolineIntent(intent, highlightMenuKey)
+        }
+        return startTrampolineIntent(trampolineIntent)
+    }
+
+    /**
+     * Returns the deep link trampoline intent for large screen devices.
+     */
+    @JvmStatic
+    fun getTrampolineIntent(intent: Intent, highlightMenuKey: String?): Intent {
+        val detailIntent = Intent(intent)
+        // Guard against the arbitrary Intent injection.
+        if (detailIntent.selector != null) {
+            detailIntent.setSelector(null)
+        }
+        // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it.
+        return Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY).apply {
+            setPackage(Utils.SETTINGS_PACKAGE_NAME)
+            replaceExtras(detailIntent)
+
+            // Relay detail intent data to prevent failure of Intent#ParseUri.
+            // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme
+            // of Intent#getData() and it may not be the scheme of an Intent.
+            putExtra(
+                SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
+                detailIntent.data
+            )
+            detailIntent.setData(null)
+            putExtra(
+                Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
+                detailIntent.toUri(Intent.URI_INTENT_SCHEME)
+            )
+            putExtra(
+                Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
+                highlightMenuKey
+            )
+            addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
+        }
+    }
+
+    private fun Context.startTrampolineIntent(trampolineIntent: Intent): Boolean = try {
+        val userInfo = userManager.getUserInfo(user.identifier)
+        if (userInfo.isManagedProfile) {
+            trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal::class.java)
+                .putExtra(SettingsActivity.EXTRA_USER_HANDLE, user)
+            startActivityAsUser(
+                trampolineIntent,
+                userManager.getProfileParent(userInfo.id).userHandle
+            )
+        } else {
+            startActivity(trampolineIntent)
+        }
+        true
+    } catch (e: ActivityNotFoundException) {
+        Log.e(TAG, "Deep link homepage is not available to show 2-pane UI")
+        false
+    }
+}
diff --git a/src/com/android/settings/search/SearchResultTrampoline.java b/src/com/android/settings/search/SearchResultTrampoline.java
index f72b097..5d897af 100644
--- a/src/com/android/settings/search/SearchResultTrampoline.java
+++ b/src/com/android/settings/search/SearchResultTrampoline.java
@@ -18,6 +18,7 @@
 
 import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
 import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB;
+import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent;
 
 import android.app.Activity;
 import android.content.ComponentName;
@@ -107,7 +108,7 @@
             startActivity(intent);
         } else if (isSettingsIntelligence(callingActivity)) {
             if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
-                startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey)
+                startActivity(getTrampolineIntent(intent, highlightMenuKey)
                         .setClass(this, DeepLinkHomepageActivityInternal.class)
                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS));
@@ -130,7 +131,7 @@
             }
         } else {
             // Two-pane case
-            startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey)
+            startActivity(getTrampolineIntent(intent, highlightMenuKey)
                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
         }
 
diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt
index 2b52b21..e5bee8b 100644
--- a/src/com/android/settings/spa/SpaActivity.kt
+++ b/src/com/android/settings/spa/SpaActivity.kt
@@ -16,18 +16,14 @@
 
 package com.android.settings.spa
 
-import android.app.ActivityManager
 import android.content.Context
 import android.content.Intent
-import android.os.RemoteException
-import android.os.UserHandle
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
 import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.util.SESSION_BROWSE
-import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
 import com.android.settingslib.spa.framework.util.appendSpaParams
 import com.google.android.setupcompat.util.WizardManagerHelper
 
@@ -44,7 +40,7 @@
         @VisibleForTesting
         fun Context.isSuwAndPageBlocked(name: String): Boolean =
             if (name in SuwBlockedPages && !WizardManagerHelper.isDeviceProvisioned(this)) {
-                Log.w(TAG, "$name blocked before SUW completed.");
+                Log.w(TAG, "$name blocked before SUW completed.")
                 true
             } else {
                 false
@@ -54,29 +50,8 @@
         fun Context.startSpaActivity(destination: String) {
             val intent = Intent(this, SpaActivity::class.java)
                 .appendSpaParams(destination = destination)
-            if (isLaunchedFromInternal()) {
-                intent.appendSpaParams(sessionName = SESSION_BROWSE)
-            } else {
-                intent.appendSpaParams(sessionName = SESSION_EXTERNAL)
-            }
+                .appendSpaParams(sessionName = SESSION_BROWSE)
             startActivity(intent)
         }
-
-        @JvmStatic
-        fun Context.startSpaActivityForApp(destinationPrefix: String, intent: Intent): Boolean {
-            val packageName = intent.data?.schemeSpecificPart ?: return false
-            startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}")
-            return true
-        }
-
-        fun Context.isLaunchedFromInternal(): Boolean {
-            var pkg: String? = null
-            try {
-                pkg = ActivityManager.getService().getLaunchedFromPackage(getActivityToken())
-            } catch (e: RemoteException) {
-                Log.v(TAG, "Could not talk to activity manager.", e)
-            }
-            return applicationContext.packageName == pkg
-        }
     }
 }
diff --git a/src/com/android/settings/spa/SpaAppBridgeActivity.kt b/src/com/android/settings/spa/SpaAppBridgeActivity.kt
index 9177939..1a77442 100644
--- a/src/com/android/settings/spa/SpaAppBridgeActivity.kt
+++ b/src/com/android/settings/spa/SpaAppBridgeActivity.kt
@@ -17,9 +17,11 @@
 package com.android.settings.spa
 
 import android.app.Activity
+import android.content.Intent
 import android.os.Bundle
-import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
+import android.os.UserHandle
 import com.android.settings.spa.SpaBridgeActivity.Companion.getDestination
+import com.android.settings.spa.SpaBridgeActivity.Companion.startSpaActivityFromBridge
 
 /**
  * Activity used as a bridge to [SpaActivity] with package scheme for application usage.
@@ -31,9 +33,18 @@
 class SpaAppBridgeActivity : Activity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        getDestination()?.let { destination ->
-            startSpaActivityForApp(destination, intent)
+        getDestination()?.let { destinationPrefix ->
+            getDestinationForApp(destinationPrefix, intent)?.let { destination ->
+                startSpaActivityFromBridge(destination)
+            }
         }
         finish()
     }
+
+    companion object {
+        fun getDestinationForApp(destinationPrefix: String, intent: Intent): String? {
+            val packageName = intent.data?.schemeSpecificPart ?: return null
+            return "$destinationPrefix/$packageName/${UserHandle.myUserId()}"
+        }
+    }
 }
diff --git a/src/com/android/settings/spa/SpaBridgeActivity.kt b/src/com/android/settings/spa/SpaBridgeActivity.kt
index 904be88..0e239ae 100644
--- a/src/com/android/settings/spa/SpaBridgeActivity.kt
+++ b/src/com/android/settings/spa/SpaBridgeActivity.kt
@@ -17,10 +17,15 @@
 package com.android.settings.spa
 
 import android.app.Activity
+import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ComponentInfoFlags
 import android.os.Bundle
-import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+import androidx.annotation.VisibleForTesting
+import com.android.settings.activityembedding.ActivityEmbeddingUtils
+import com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink
+import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
+import com.android.settingslib.spa.framework.util.appendSpaParams
 
 /**
  * Activity used as a bridge to [SpaActivity].
@@ -33,17 +38,28 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         getDestination()?.let { destination ->
-            startSpaActivity(destination)
+            startSpaActivityFromBridge(destination)
         }
         finish()
     }
 
     companion object {
+        fun Activity.startSpaActivityFromBridge(destination: String) {
+            val intent = Intent(this, SpaActivity::class.java)
+                .appendSpaParams(destination = destination)
+                .appendSpaParams(sessionName = SESSION_EXTERNAL)
+            if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) ||
+                !tryStartMultiPaneDeepLink(intent)) {
+                startActivity(intent)
+            }
+        }
+
         fun Activity.getDestination(): String? =
             packageManager.getActivityInfo(
                 componentName, ComponentInfoFlags.of(PackageManager.GET_META_DATA.toLong())
             ).metaData.getString(META_DATA_KEY_DESTINATION)
 
-        private const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION"
+        @VisibleForTesting
+        const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION"
     }
 }
diff --git a/tests/robotests/src/com/android/settings/SettingsActivityTest.java b/tests/robotests/src/com/android/settings/SettingsActivityTest.java
index 696fd4c..89f8449 100644
--- a/tests/robotests/src/com/android/settings/SettingsActivityTest.java
+++ b/tests/robotests/src/com/android/settings/SettingsActivityTest.java
@@ -16,8 +16,6 @@
 
 package com.android.settings;
 
-import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI;
-
 import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -32,7 +30,6 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
-import android.net.Uri;
 
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
@@ -52,7 +49,6 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -118,29 +114,6 @@
         assertThat(((ListenerFragment) fragments.get(1)).mOnActivityResultCalled).isTrue();
     }
 
-    @Test
-    public void getTrampolineIntent_intentSelector_shouldNotChangeIntentAction() {
-        Intent targetIntent = new Intent().setClassName("android",
-                "com.android.internal.app.PlatLogoActivity");
-        Intent intent = new Intent(android.provider.Settings.ACTION_DISPLAY_SETTINGS);
-        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
-        intent.setSelector(new Intent().setData(
-                Uri.fromParts(targetIntent.toUri(Intent.URI_INTENT_SCHEME), /* ssp= */ "",
-                /* fragment= */ null)));
-
-        Intent resultIntent = SettingsActivity.getTrampolineIntent(intent, "menu_key");
-
-        String intentUriString =
-                resultIntent.getStringExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI);
-        Intent parsedIntent = null;
-        try {
-            parsedIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
-        } catch (URISyntaxException e) {
-            // Do nothng.
-        }
-        assertThat(parsedIntent.getAction()).isEqualTo(intent.getAction());
-    }
-
     public static class ListenerFragment extends Fragment implements OnActivityResultListener {
 
         private boolean mOnActivityResultCalled;
diff --git a/tests/spa_unit/src/com/android/settings/activityembedding/EmbeddedDeepLinkUtilsTest.kt b/tests/spa_unit/src/com/android/settings/activityembedding/EmbeddedDeepLinkUtilsTest.kt
new file mode 100644
index 0000000..9a638b2
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/activityembedding/EmbeddedDeepLinkUtilsTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.activityembedding
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.Settings
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class EmbeddedDeepLinkUtilsTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun getTrampolineIntent_intentSelector_shouldNotChangeIntentAction() {
+        val targetIntent = Intent().setClassName(
+            "android",
+            "com.android.internal.app.PlatLogoActivity"
+        )
+        val intent = Intent(Settings.ACTION_DISPLAY_SETTINGS).apply {
+            setComponent(resolveActivity(context.packageManager))
+            setSelector(
+                Intent().setData(
+                    Uri.fromParts(
+                        targetIntent.toUri(Intent.URI_INTENT_SCHEME),
+                        /* ssp= */ "",
+                        /* fragment= */ null,
+                    )
+                )
+            )
+        }
+
+        val resultIntent = getTrampolineIntent(intent, "menu_key")
+
+        val intentUriString =
+            resultIntent.getStringExtra(Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI)
+        val parsedIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME)
+        assertThat(parsedIntent.action).isEqualTo(intent.action)
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt
index 1b2a7b1..ec81c80 100644
--- a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt
@@ -18,13 +18,10 @@
 
 import android.content.Context
 import android.content.Intent
-import android.net.Uri
-import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.settings.spa.SpaActivity.Companion.isSuwAndPageBlocked
 import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
-import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
 import com.android.settings.spa.app.AllAppListPageProvider
 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
 import com.android.settingslib.spa.framework.util.KEY_DESTINATION
@@ -34,19 +31,18 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.MockitoSession
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
-import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidJUnit4::class)
 class SpaActivityTest {
     private lateinit var mockSession: MockitoSession
 
-    @Mock
-    private lateinit var context: Context
+    private val context = mock<Context>()
 
     @Before
     fun setUp() {
@@ -71,7 +67,7 @@
     }
 
     @Test
-    fun isSuwAndPageBlocked_blocklistedPageInSuw_blocked() {
+    fun isSuwAndPageBlocked_suwBlockedPageInSuw_blocked() {
         whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(false)
 
         val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name)
@@ -80,7 +76,7 @@
     }
 
     @Test
-    fun isSuwAndPageBlocked_blocklistedPageNotInSuw_notBlocked() {
+    fun isSuwAndPageBlocked_SuwBlockedPageNotInSuw_notBlocked() {
         whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(true)
 
         val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name)
@@ -92,31 +88,14 @@
     fun startSpaActivity() {
         context.startSpaActivity(DESTINATION)
 
-        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(context).startActivity(intentCaptor.capture())
-        val intent = intentCaptor.value
+        val intent = argumentCaptor<Intent> {
+            verify(context).startActivity(capture())
+        }.firstValue
         assertThat(intent.component?.className).isEqualTo(SpaActivity::class.qualifiedName)
         assertThat(intent.getStringExtra(KEY_DESTINATION)).isEqualTo(DESTINATION)
     }
 
-    @Test
-    fun startSpaActivityForApp() {
-        val intent = Intent().apply {
-            data = Uri.parse("package:$PACKAGE_NAME")
-        }
-
-        context.startSpaActivityForApp(DESTINATION, intent)
-
-        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(context).startActivity(intentCaptor.capture())
-        val capturedIntent = intentCaptor.value
-        assertThat(capturedIntent.component?.className).isEqualTo(SpaActivity::class.qualifiedName)
-        assertThat(capturedIntent.getStringExtra(KEY_DESTINATION))
-            .isEqualTo("Destination/package.name/${UserHandle.myUserId()}")
-    }
-
     private companion object {
         const val DESTINATION = "Destination"
-        const val PACKAGE_NAME = "package.name"
     }
 }
diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaAppBridgeActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaAppBridgeActivityTest.kt
new file mode 100644
index 0000000..be2b5e0
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/SpaAppBridgeActivityTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa
+
+import android.content.Intent
+import android.net.Uri
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.spa.SpaAppBridgeActivity.Companion.getDestinationForApp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaAppBridgeActivityTest {
+    @Test
+    fun getDestinationForApp_hasPackageName() {
+        val intent = Intent().apply {
+            data = Uri.parse("package:${PACKAGE_NAME}")
+        }
+
+        val destination = getDestinationForApp(DESTINATION, intent)
+
+        assertThat(destination).isEqualTo("$DESTINATION/$PACKAGE_NAME/${UserHandle.myUserId()}")
+    }
+
+    @Test
+    fun getDestinationForApp_noPackageName() {
+        val intent = Intent()
+
+        val destination = getDestinationForApp(DESTINATION, intent)
+
+        assertThat(destination).isNull()
+    }
+
+    private companion object {
+        const val DESTINATION = "Destination"
+        const val PACKAGE_NAME = "package.name"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt
new file mode 100644
index 0000000..48fa823
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
+import androidx.core.os.bundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.spa.SpaBridgeActivity.Companion.META_DATA_KEY_DESTINATION
+import com.android.settings.spa.SpaBridgeActivity.Companion.getDestination
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class SpaBridgeActivityTest {
+    private val mockPackageManager = mock<PackageManager> {
+        on { getActivityInfo(eq(COMPONENT_NAME), any<ComponentInfoFlags>()) } doReturn
+            ActivityInfo().apply {
+                metaData = bundleOf(META_DATA_KEY_DESTINATION to DESTINATION)
+            }
+    }
+
+    private val activity = mock<Activity> {
+        on { componentName } doReturn COMPONENT_NAME
+        on { packageManager } doReturn mockPackageManager
+    }
+
+    @Test
+    fun getDestination() {
+        val destination = activity.getDestination()
+
+        assertThat(destination).isEqualTo(DESTINATION)
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+        const val ACTIVITY_NAME = "ActivityName"
+        val COMPONENT_NAME = ComponentName(PACKAGE_NAME, ACTIVITY_NAME)
+        const val DESTINATION = "Destination"
+    }
+}