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; package com.android.settings;
import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY; import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink;
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.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING; import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@@ -35,7 +31,6 @@ import android.content.SharedPreferences;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.Resources.Theme; import android.content.res.Resources.Theme;
import android.graphics.drawable.Icon; 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.SubSettingLauncher;
import com.android.settings.core.gateway.SettingsGateway; import com.android.settings.core.gateway.SettingsGateway;
import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.homepage.TopLevelSettings; import com.android.settings.homepage.TopLevelSettings;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
@@ -278,7 +272,8 @@ public class SettingsActivity extends SettingsBaseActivity
getMetaData(); getMetaData();
final Intent intent = getIntent(); final Intent intent = getIntent();
if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) { if (shouldShowMultiPaneDeepLink(intent)
&& tryStartMultiPaneDeepLink(this, intent, mHighlightMenuKey)) {
finish(); finish();
super.onCreate(savedState); super.onCreate(savedState);
return; return;
@@ -415,73 +410,7 @@ public class SettingsActivity extends SettingsBaseActivity
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
} }
/** private boolean shouldShowMultiPaneDeepLink(Intent intent) {
* 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) {
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
return false; 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.PictureInPictureDetails
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity 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.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
@@ -72,17 +72,18 @@ object SettingsActivityUtil {
@JvmStatic @JvmStatic
fun Context.launchSpaActivity(fragmentName: String, intent: Intent): Boolean { fun Context.launchSpaActivity(fragmentName: String, intent: Intent): Boolean {
if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) { if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
return false getDestination(fragmentName, intent)?.let { destination ->
} startSpaActivity(destination)
FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]?.let { destination -> return true
startSpaActivity(destination) }
return true
}
FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { appDestinationPrefix ->
startSpaActivityForApp(appDestinationPrefix, intent)
return true
} }
return false 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_ARGUMENTS;
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB; import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB;
import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent;
import android.app.Activity; import android.app.Activity;
import android.content.ComponentName; import android.content.ComponentName;
@@ -107,7 +108,7 @@ public class SearchResultTrampoline extends Activity {
startActivity(intent); startActivity(intent);
} else if (isSettingsIntelligence(callingActivity)) { } else if (isSettingsIntelligence(callingActivity)) {
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) { if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey) startActivity(getTrampolineIntent(intent, highlightMenuKey)
.setClass(this, DeepLinkHomepageActivityInternal.class) .setClass(this, DeepLinkHomepageActivityInternal.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)); | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS));
@@ -130,7 +131,7 @@ public class SearchResultTrampoline extends Activity {
} }
} else { } else {
// Two-pane case // Two-pane case
startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey) startActivity(getTrampolineIntent(intent, highlightMenuKey)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} }

View File

@@ -16,18 +16,14 @@
package com.android.settings.spa package com.android.settings.spa
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log import android.util.Log
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
import com.android.settingslib.spa.framework.BrowseActivity import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.common.SettingsPage 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_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
import com.android.settingslib.spa.framework.util.appendSpaParams import com.android.settingslib.spa.framework.util.appendSpaParams
import com.google.android.setupcompat.util.WizardManagerHelper import com.google.android.setupcompat.util.WizardManagerHelper
@@ -44,7 +40,7 @@ class SpaActivity : BrowseActivity() {
@VisibleForTesting @VisibleForTesting
fun Context.isSuwAndPageBlocked(name: String): Boolean = fun Context.isSuwAndPageBlocked(name: String): Boolean =
if (name in SuwBlockedPages && !WizardManagerHelper.isDeviceProvisioned(this)) { if (name in SuwBlockedPages && !WizardManagerHelper.isDeviceProvisioned(this)) {
Log.w(TAG, "$name blocked before SUW completed."); Log.w(TAG, "$name blocked before SUW completed.")
true true
} else { } else {
false false
@@ -54,29 +50,8 @@ class SpaActivity : BrowseActivity() {
fun Context.startSpaActivity(destination: String) { fun Context.startSpaActivity(destination: String) {
val intent = Intent(this, SpaActivity::class.java) val intent = Intent(this, SpaActivity::class.java)
.appendSpaParams(destination = destination) .appendSpaParams(destination = destination)
if (isLaunchedFromInternal()) { .appendSpaParams(sessionName = SESSION_BROWSE)
intent.appendSpaParams(sessionName = SESSION_BROWSE)
} else {
intent.appendSpaParams(sessionName = SESSION_EXTERNAL)
}
startActivity(intent) 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 package com.android.settings.spa
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.os.Bundle 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.getDestination
import com.android.settings.spa.SpaBridgeActivity.Companion.startSpaActivityFromBridge
/** /**
* Activity used as a bridge to [SpaActivity] with package scheme for application usage. * 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() { class SpaAppBridgeActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
getDestination()?.let { destination -> getDestination()?.let { destinationPrefix ->
startSpaActivityForApp(destination, intent) getDestinationForApp(destinationPrefix, intent)?.let { destination ->
startSpaActivityFromBridge(destination)
}
} }
finish() 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 package com.android.settings.spa
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.ComponentInfoFlags import android.content.pm.PackageManager.ComponentInfoFlags
import android.os.Bundle 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]. * Activity used as a bridge to [SpaActivity].
@@ -33,17 +38,28 @@ class SpaBridgeActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
getDestination()?.let { destination -> getDestination()?.let { destination ->
startSpaActivity(destination) startSpaActivityFromBridge(destination)
} }
finish() finish()
} }
companion object { 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? = fun Activity.getDestination(): String? =
packageManager.getActivityInfo( packageManager.getActivityInfo(
componentName, ComponentInfoFlags.of(PackageManager.GET_META_DATA.toLong()) componentName, ComponentInfoFlags.of(PackageManager.GET_META_DATA.toLong())
).metaData.getString(META_DATA_KEY_DESTINATION) ).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; 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.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -32,7 +30,6 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@@ -52,7 +49,6 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -118,29 +114,6 @@ public class SettingsActivityTest {
assertThat(((ListenerFragment) fragments.get(1)).mOnActivityResultCalled).isTrue(); 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 { public static class ListenerFragment extends Fragment implements OnActivityResultListener {
private boolean mOnActivityResultCalled; 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.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.settings.spa.SpaActivity.Companion.isSuwAndPageBlocked import com.android.settings.spa.SpaActivity.Companion.isSuwAndPageBlocked
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity 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.AllAppListPageProvider
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
import com.android.settingslib.spa.framework.util.KEY_DESTINATION import com.android.settingslib.spa.framework.util.KEY_DESTINATION
@@ -34,19 +31,18 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith 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.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.quality.Strictness
import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SpaActivityTest { class SpaActivityTest {
private lateinit var mockSession: MockitoSession private lateinit var mockSession: MockitoSession
@Mock private val context = mock<Context>()
private lateinit var context: Context
@Before @Before
fun setUp() { fun setUp() {
@@ -71,7 +67,7 @@ class SpaActivityTest {
} }
@Test @Test
fun isSuwAndPageBlocked_blocklistedPageInSuw_blocked() { fun isSuwAndPageBlocked_suwBlockedPageInSuw_blocked() {
whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(false) whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(false)
val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name) val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name)
@@ -80,7 +76,7 @@ class SpaActivityTest {
} }
@Test @Test
fun isSuwAndPageBlocked_blocklistedPageNotInSuw_notBlocked() { fun isSuwAndPageBlocked_SuwBlockedPageNotInSuw_notBlocked() {
whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(true) whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(true)
val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name) val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name)
@@ -92,31 +88,14 @@ class SpaActivityTest {
fun startSpaActivity() { fun startSpaActivity() {
context.startSpaActivity(DESTINATION) context.startSpaActivity(DESTINATION)
val intentCaptor = ArgumentCaptor.forClass(Intent::class.java) val intent = argumentCaptor<Intent> {
verify(context).startActivity(intentCaptor.capture()) verify(context).startActivity(capture())
val intent = intentCaptor.value }.firstValue
assertThat(intent.component?.className).isEqualTo(SpaActivity::class.qualifiedName) assertThat(intent.component?.className).isEqualTo(SpaActivity::class.qualifiedName)
assertThat(intent.getStringExtra(KEY_DESTINATION)).isEqualTo(DESTINATION) 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 { private companion object {
const val DESTINATION = "Destination" 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"
}
}