Loading src/com/android/settings/spa/SpaBridgeActivity.kt +28 −19 Original line number Diff line number Diff line Loading @@ -17,20 +17,18 @@ package com.android.settings.spa import android.app.Activity import android.content.Intent import android.content.pm.PackageManager.ComponentInfoFlags import android.content.pm.PackageManager.GET_META_DATA import android.os.Bundle import com.android.settings.activityembedding.ActivityEmbeddingUtils import com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink import com.android.settings.spa.SpaDestination.Companion.getDestination import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL import com.android.settingslib.spa.framework.util.appendSpaParams import androidx.annotation.VisibleForTesting import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY /** * Activity used as a bridge to [SpaActivity]. * * Since [SpaActivity] is not exported, [SpaActivity] could not be the target activity of * <activity-alias>, otherwise all its pages will be exported. * So need this bridge activity to sit in the middle of <activity-alias> and [SpaActivity]. * <activity-alias>, otherwise all its pages will be exported. So need this bridge activity to sit * in the middle of <activity-alias> and [SpaActivity]. */ class SpaBridgeActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { Loading @@ -41,17 +39,28 @@ class SpaBridgeActivity : Activity() { companion object { fun Activity.startSpaActivityFromBridge(destinationFactory: (String) -> String? = { it }) { val (destination, highlightMenuKey) = getDestination(destinationFactory) ?: return val intent = Intent(this, SpaActivity::class.java) .appendSpaParams( destination = destination, sessionName = SESSION_EXTERNAL, ) if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) || !tryStartMultiPaneDeepLink(intent, highlightMenuKey) ) { startActivity(intent) getDestination(destinationFactory)?.startFromExportedActivity(this) } @VisibleForTesting fun Activity.getDestination( destinationFactory: (String) -> String? = { it }, ): SpaDestination? { val metaData = packageManager .getActivityInfo(componentName, ComponentInfoFlags.of(GET_META_DATA.toLong())) .metaData val destination = metaData.getString(META_DATA_KEY_DESTINATION) if (destination.isNullOrBlank()) return null val finalDestination = destinationFactory(destination) if (finalDestination.isNullOrBlank()) return null return SpaDestination( destination = finalDestination, highlightMenuKey = metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY), ) } @VisibleForTesting const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION" } } src/com/android/settings/spa/SpaDestination.kt +14 −21 Original line number Diff line number Diff line Loading @@ -17,33 +17,26 @@ package com.android.settings.spa import android.app.Activity import android.content.pm.PackageManager import androidx.annotation.VisibleForTesting import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY import android.content.Intent 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 data class SpaDestination( val destination: String, val highlightMenuKey: String?, ) { companion object { fun Activity.getDestination( destinationFactory: (String) -> String? = { it }, ): SpaDestination? { val metaData = packageManager.getActivityInfo( componentName, PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA.toLong()) ).metaData val destination = metaData.getString(META_DATA_KEY_DESTINATION) if (destination.isNullOrBlank()) return null val finalDestination = destinationFactory(destination) if (finalDestination.isNullOrBlank()) return null return SpaDestination( destination = finalDestination, highlightMenuKey = metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY), fun startFromExportedActivity(activity: Activity) { val intent = Intent(activity, SpaActivity::class.java) .appendSpaParams( destination = destination, sessionName = SESSION_EXTERNAL, ) if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) || !activity.tryStartMultiPaneDeepLink(intent, highlightMenuKey) ) { activity.startActivity(intent) } @VisibleForTesting const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION" } } tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.os.Bundle import androidx.core.os.bundleOf import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY 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.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) class SpaBridgeActivityTest { private var activityMetadata: Bundle = bundleOf() private val mockPackageManager = mock<PackageManager> { on { getActivityInfo(eq(COMPONENT_NAME), any<PackageManager.ComponentInfoFlags>()) } doAnswer { ActivityInfo().apply { metaData = activityMetadata } } } private val activity = mock<Activity> { on { componentName } doReturn COMPONENT_NAME on { packageManager } doReturn mockPackageManager } @Test fun getDestination_noDestination_returnNull() { activityMetadata = bundleOf() val destination = activity.getDestination() assertThat(destination).isNull() } @Test fun getDestination_withoutHighlightMenuKey() { activityMetadata = bundleOf(META_DATA_KEY_DESTINATION to DESTINATION) val (destination, highlightMenuKey) = activity.getDestination()!! assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isNull() } @Test fun getDestination_withHighlightMenuKey() { activityMetadata = bundleOf( META_DATA_KEY_DESTINATION to DESTINATION, META_DATA_KEY_HIGHLIGHT_MENU_KEY to HIGHLIGHT_MENU_KEY, ) val (destination, highlightMenuKey) = activity.getDestination()!! assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isEqualTo(HIGHLIGHT_MENU_KEY) } 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" const val HIGHLIGHT_MENU_KEY = "apps" } } tests/spa_unit/src/com/android/settings/spa/SpaDestinationTest.kt +12 −61 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2024 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. Loading @@ -17,81 +17,32 @@ 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.os.Bundle import androidx.core.os.bundleOf import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY import com.android.settings.spa.SpaDestination.Companion.META_DATA_KEY_DESTINATION import com.android.settings.spa.SpaDestination.Companion.getDestination import com.google.common.truth.Truth.assertThat import com.android.settingslib.spa.framework.util.KEY_DESTINATION import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class SpaDestinationTest { private var activityMetadata: Bundle = bundleOf() private val mockPackageManager = mock<PackageManager> { on { getActivityInfo( eq(COMPONENT_NAME), any<PackageManager.ComponentInfoFlags>() ) } doAnswer { ActivityInfo().apply { metaData = activityMetadata } } } private val activity = mock<Activity> { on { componentName } doReturn COMPONENT_NAME on { packageManager } doReturn mockPackageManager } @Test fun getDestination_noDestination_returnNull() { activityMetadata = bundleOf() val destination = activity.getDestination() assertThat(destination).isNull() } @Test fun getDestination_withoutHighlightMenuKey() { activityMetadata = bundleOf(META_DATA_KEY_DESTINATION to DESTINATION) val (destination, highlightMenuKey) = activity.getDestination()!! assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isNull() } private val activity = mock<Activity>() @Test fun getDestination_withHighlightMenuKey() { activityMetadata = bundleOf( META_DATA_KEY_DESTINATION to DESTINATION, META_DATA_KEY_HIGHLIGHT_MENU_KEY to HIGHLIGHT_MENU_KEY, ) fun startFromExportedActivity() { val spaDestination = SpaDestination(destination = DESTINATION, highlightMenuKey = null) val (destination, highlightMenuKey) = activity.getDestination()!! spaDestination.startFromExportedActivity(activity) assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isEqualTo(HIGHLIGHT_MENU_KEY) verify(activity).startActivity(argThat { component!!.className == SpaActivity::class.qualifiedName getStringExtra(KEY_DESTINATION) == 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" const val HIGHLIGHT_MENU_KEY = "apps" } } Loading
src/com/android/settings/spa/SpaBridgeActivity.kt +28 −19 Original line number Diff line number Diff line Loading @@ -17,20 +17,18 @@ package com.android.settings.spa import android.app.Activity import android.content.Intent import android.content.pm.PackageManager.ComponentInfoFlags import android.content.pm.PackageManager.GET_META_DATA import android.os.Bundle import com.android.settings.activityembedding.ActivityEmbeddingUtils import com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink import com.android.settings.spa.SpaDestination.Companion.getDestination import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL import com.android.settingslib.spa.framework.util.appendSpaParams import androidx.annotation.VisibleForTesting import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY /** * Activity used as a bridge to [SpaActivity]. * * Since [SpaActivity] is not exported, [SpaActivity] could not be the target activity of * <activity-alias>, otherwise all its pages will be exported. * So need this bridge activity to sit in the middle of <activity-alias> and [SpaActivity]. * <activity-alias>, otherwise all its pages will be exported. So need this bridge activity to sit * in the middle of <activity-alias> and [SpaActivity]. */ class SpaBridgeActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { Loading @@ -41,17 +39,28 @@ class SpaBridgeActivity : Activity() { companion object { fun Activity.startSpaActivityFromBridge(destinationFactory: (String) -> String? = { it }) { val (destination, highlightMenuKey) = getDestination(destinationFactory) ?: return val intent = Intent(this, SpaActivity::class.java) .appendSpaParams( destination = destination, sessionName = SESSION_EXTERNAL, ) if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) || !tryStartMultiPaneDeepLink(intent, highlightMenuKey) ) { startActivity(intent) getDestination(destinationFactory)?.startFromExportedActivity(this) } @VisibleForTesting fun Activity.getDestination( destinationFactory: (String) -> String? = { it }, ): SpaDestination? { val metaData = packageManager .getActivityInfo(componentName, ComponentInfoFlags.of(GET_META_DATA.toLong())) .metaData val destination = metaData.getString(META_DATA_KEY_DESTINATION) if (destination.isNullOrBlank()) return null val finalDestination = destinationFactory(destination) if (finalDestination.isNullOrBlank()) return null return SpaDestination( destination = finalDestination, highlightMenuKey = metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY), ) } @VisibleForTesting const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION" } }
src/com/android/settings/spa/SpaDestination.kt +14 −21 Original line number Diff line number Diff line Loading @@ -17,33 +17,26 @@ package com.android.settings.spa import android.app.Activity import android.content.pm.PackageManager import androidx.annotation.VisibleForTesting import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY import android.content.Intent 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 data class SpaDestination( val destination: String, val highlightMenuKey: String?, ) { companion object { fun Activity.getDestination( destinationFactory: (String) -> String? = { it }, ): SpaDestination? { val metaData = packageManager.getActivityInfo( componentName, PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA.toLong()) ).metaData val destination = metaData.getString(META_DATA_KEY_DESTINATION) if (destination.isNullOrBlank()) return null val finalDestination = destinationFactory(destination) if (finalDestination.isNullOrBlank()) return null return SpaDestination( destination = finalDestination, highlightMenuKey = metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY), fun startFromExportedActivity(activity: Activity) { val intent = Intent(activity, SpaActivity::class.java) .appendSpaParams( destination = destination, sessionName = SESSION_EXTERNAL, ) if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) || !activity.tryStartMultiPaneDeepLink(intent, highlightMenuKey) ) { activity.startActivity(intent) } @VisibleForTesting const val META_DATA_KEY_DESTINATION = "com.android.settings.spa.DESTINATION" } }
tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.os.Bundle import androidx.core.os.bundleOf import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY 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.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) class SpaBridgeActivityTest { private var activityMetadata: Bundle = bundleOf() private val mockPackageManager = mock<PackageManager> { on { getActivityInfo(eq(COMPONENT_NAME), any<PackageManager.ComponentInfoFlags>()) } doAnswer { ActivityInfo().apply { metaData = activityMetadata } } } private val activity = mock<Activity> { on { componentName } doReturn COMPONENT_NAME on { packageManager } doReturn mockPackageManager } @Test fun getDestination_noDestination_returnNull() { activityMetadata = bundleOf() val destination = activity.getDestination() assertThat(destination).isNull() } @Test fun getDestination_withoutHighlightMenuKey() { activityMetadata = bundleOf(META_DATA_KEY_DESTINATION to DESTINATION) val (destination, highlightMenuKey) = activity.getDestination()!! assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isNull() } @Test fun getDestination_withHighlightMenuKey() { activityMetadata = bundleOf( META_DATA_KEY_DESTINATION to DESTINATION, META_DATA_KEY_HIGHLIGHT_MENU_KEY to HIGHLIGHT_MENU_KEY, ) val (destination, highlightMenuKey) = activity.getDestination()!! assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isEqualTo(HIGHLIGHT_MENU_KEY) } 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" const val HIGHLIGHT_MENU_KEY = "apps" } }
tests/spa_unit/src/com/android/settings/spa/SpaDestinationTest.kt +12 −61 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2024 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. Loading @@ -17,81 +17,32 @@ 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.os.Bundle import androidx.core.os.bundleOf import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.SettingsActivity.META_DATA_KEY_HIGHLIGHT_MENU_KEY import com.android.settings.spa.SpaDestination.Companion.META_DATA_KEY_DESTINATION import com.android.settings.spa.SpaDestination.Companion.getDestination import com.google.common.truth.Truth.assertThat import com.android.settingslib.spa.framework.util.KEY_DESTINATION import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class SpaDestinationTest { private var activityMetadata: Bundle = bundleOf() private val mockPackageManager = mock<PackageManager> { on { getActivityInfo( eq(COMPONENT_NAME), any<PackageManager.ComponentInfoFlags>() ) } doAnswer { ActivityInfo().apply { metaData = activityMetadata } } } private val activity = mock<Activity> { on { componentName } doReturn COMPONENT_NAME on { packageManager } doReturn mockPackageManager } @Test fun getDestination_noDestination_returnNull() { activityMetadata = bundleOf() val destination = activity.getDestination() assertThat(destination).isNull() } @Test fun getDestination_withoutHighlightMenuKey() { activityMetadata = bundleOf(META_DATA_KEY_DESTINATION to DESTINATION) val (destination, highlightMenuKey) = activity.getDestination()!! assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isNull() } private val activity = mock<Activity>() @Test fun getDestination_withHighlightMenuKey() { activityMetadata = bundleOf( META_DATA_KEY_DESTINATION to DESTINATION, META_DATA_KEY_HIGHLIGHT_MENU_KEY to HIGHLIGHT_MENU_KEY, ) fun startFromExportedActivity() { val spaDestination = SpaDestination(destination = DESTINATION, highlightMenuKey = null) val (destination, highlightMenuKey) = activity.getDestination()!! spaDestination.startFromExportedActivity(activity) assertThat(destination).isEqualTo(DESTINATION) assertThat(highlightMenuKey).isEqualTo(HIGHLIGHT_MENU_KEY) verify(activity).startActivity(argThat { component!!.className == SpaActivity::class.qualifiedName getStringExtra(KEY_DESTINATION) == 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" const val HIGHLIGHT_MENU_KEY = "apps" } }