From 7ef8b0c5184cebc36f7ee142891d5c7a81ebcdd0 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 27 Jun 2024 13:06:38 +0800 Subject: [PATCH] Export SpaDestination.startFromExportedActivity To be called from other exported activity in the future. Bug: 346776183 Flag: EXEMPT refactor Test: adb shell am start \ -a android.settings.REQUEST_MEDIA_ROUTING_CONTROL Test: unit test Change-Id: Ica105ab3b56d33e4cd2fe1bb1c1218ef2f219ab3 --- .../android/settings/spa/SpaBridgeActivity.kt | 47 +++++---- .../android/settings/spa/SpaDestination.kt | 35 +++---- .../settings/spa/SpaBridgeActivityTest.kt | 95 +++++++++++++++++++ .../settings/spa/SpaDestinationTest.kt | 73 +++----------- 4 files changed, 149 insertions(+), 101 deletions(-) create mode 100644 tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt diff --git a/src/com/android/settings/spa/SpaBridgeActivity.kt b/src/com/android/settings/spa/SpaBridgeActivity.kt index 61d8f514947..d579fdf65d3 100644 --- a/src/com/android/settings/spa/SpaBridgeActivity.kt +++ b/src/com/android/settings/spa/SpaBridgeActivity.kt @@ -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 - * , otherwise all its pages will be exported. - * So need this bridge activity to sit in the middle of and [SpaActivity]. + * , otherwise all its pages will be exported. So need this bridge activity to sit + * in the middle of and [SpaActivity]. */ class SpaBridgeActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -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" } } diff --git a/src/com/android/settings/spa/SpaDestination.kt b/src/com/android/settings/spa/SpaDestination.kt index bdec1d810f9..cb20c37f28d 100644 --- a/src/com/android/settings/spa/SpaDestination.kt +++ b/src/com/android/settings/spa/SpaDestination.kt @@ -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" } } 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..e29bd967784 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/SpaBridgeActivityTest.kt @@ -0,0 +1,95 @@ +/* + * 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 { + on { + getActivityInfo(eq(COMPONENT_NAME), any()) + } doAnswer { ActivityInfo().apply { metaData = activityMetadata } } + } + + private val activity = + mock { + 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" + } +} diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaDestinationTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaDestinationTest.kt index 0b9eb228d5d..ee658c1f66a 100644 --- a/tests/spa_unit/src/com/android/settings/spa/SpaDestinationTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/SpaDestinationTest.kt @@ -1,5 +1,5 @@ /* - * 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. @@ -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 { - on { - getActivityInfo( - eq(COMPONENT_NAME), - any() - ) - } doAnswer { - ActivityInfo().apply { metaData = activityMetadata } - } - } - - private val activity = mock { - on { componentName } doReturn COMPONENT_NAME - on { packageManager } doReturn mockPackageManager - } + private val activity = mock() @Test - fun getDestination_noDestination_returnNull() { - activityMetadata = bundleOf() + fun startFromExportedActivity() { + val spaDestination = SpaDestination(destination = DESTINATION, highlightMenuKey = null) - val destination = activity.getDestination() + spaDestination.startFromExportedActivity(activity) - 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) + 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" } }