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
This commit is contained in:
Chaohui Wang
2023-11-03 17:48:35 +08:00
parent 005c47f19c
commit 0d338cd1df
12 changed files with 355 additions and 179 deletions

View File

@@ -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;
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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));
}

View File

@@ -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
}
}
}

View File

@@ -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()}"
}
}
}

View File

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

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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<Context>()
@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<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"
}
}

View File

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

View File

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