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"
+ }
+}