diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index bc061e3b028..4c20231df1e 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.SharedPreferences; 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.SettingsBaseActivity; 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 @@ public class SettingsActivity extends SettingsBaseActivity 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 @@ public class SettingsActivity extends SettingsBaseActivity 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 c23bc180491..4238ff8ba19 100644 --- a/src/com/android/settings/SettingsActivityUtil.kt +++ b/src/com/android/settings/SettingsActivityUtil.kt @@ -28,7 +28,7 @@ import com.android.settings.applications.appinfo.WriteSettingsDetails 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 @@ object SettingsActivityUtil { @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 00000000000..2bc8cda14ed --- /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 f72b097b2ad..5d897af3b35 100644 --- a/src/com/android/settings/search/SearchResultTrampoline.java +++ b/src/com/android/settings/search/SearchResultTrampoline.java @@ -18,6 +18,7 @@ package com.android.settings.search; 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 @@ public class SearchResultTrampoline extends Activity { 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 @@ public class SearchResultTrampoline extends Activity { } } 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 2b52b21af0c..e5bee8b1419 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 @@ class SpaActivity : BrowseActivity() { @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 @@ class SpaActivity : BrowseActivity() { 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 917793931bc..1a77442e261 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 @@ import com.android.settings.spa.SpaBridgeActivity.Companion.getDestination 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 904be88376d..0e239aee0b0 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 @@ class SpaBridgeActivity : Activity() { 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 696fd4cc45b..89f84496a6e 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 static org.mockito.Mockito.when; 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.RobolectricTestRunner; 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 @@ public class SettingsActivityTest { 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 00000000000..9a638b27ce5 --- /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 1b2a7b1ee90..ec81c80b372 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 @@ package com.android.settings.spa 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.After 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() @Before fun setUp() { @@ -71,7 +67,7 @@ class SpaActivityTest { } @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 @@ class SpaActivityTest { } @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 @@ class SpaActivityTest { fun startSpaActivity() { context.startSpaActivity(DESTINATION) - val intentCaptor = ArgumentCaptor.forClass(Intent::class.java) - verify(context).startActivity(intentCaptor.capture()) - val intent = intentCaptor.value + val intent = argumentCaptor { + 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 00000000000..be2b5e0bb35 --- /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 00000000000..48fa8233293 --- /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 { + on { getActivityInfo(eq(COMPONENT_NAME), any()) } doReturn + ActivityInfo().apply { + metaData = bundleOf(META_DATA_KEY_DESTINATION to DESTINATION) + } + } + + private val activity = mock { + 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" + } +}