Merge "Fix app battery usage list launch incorrect works app"
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index b2de004..8ee4eba 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -117,6 +117,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
public final class Utils extends com.android.settingslib.Utils {
@@ -589,7 +590,9 @@
return inflater.inflate(resId, parent, false);
}
- public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) {
+ /** Gets all the domains that the given package could handled. */
+ @NonNull
+ public static Set<String> getHandledDomains(PackageManager pm, String packageName) {
final List<IntentFilterVerificationInfo> iviList =
pm.getIntentFilterVerifications(packageName);
final List<IntentFilter> filters = pm.getAllIntentFilters(packageName);
@@ -597,9 +600,7 @@
final ArraySet<String> result = new ArraySet<>();
if (iviList != null && iviList.size() > 0) {
for (IntentFilterVerificationInfo ivi : iviList) {
- for (String host : ivi.getDomains()) {
- result.add(host);
- }
+ result.addAll(ivi.getDomains());
}
}
if (filters != null && filters.size() > 0) {
diff --git a/src/com/android/settings/applications/OpenSupportedLinks.java b/src/com/android/settings/applications/OpenSupportedLinks.java
index 4f5f2a8..c4e478c 100644
--- a/src/com/android/settings/applications/OpenSupportedLinks.java
+++ b/src/com/android/settings/applications/OpenSupportedLinks.java
@@ -23,7 +23,6 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.Log;
import android.view.View;
@@ -36,6 +35,8 @@
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import java.util.Set;
+
/**
* Display the Open Supported Links page. Allow users choose what kind supported links they need.
*/
@@ -195,7 +196,7 @@
@VisibleForTesting
void addLinksToFooter(FooterPreference footer) {
- final ArraySet<String> result = Utils.getHandledDomains(mPackageManager, mPackageName);
+ final Set<String> result = Utils.getHandledDomains(mPackageManager, mPackageName);
if (result.isEmpty()) {
Log.w(TAG, "Can't find any app links.");
return;
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index e3d0805..3a4d3f6 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -98,7 +98,7 @@
// TODO: notification_settings
AppPermissionPreference(app)
AppStoragePreference(app)
- // TODO: instant_app_launch_supported_domain_urls
+ InstantAppDomainsPreference(app)
AppDataUsagePreference(app)
AppTimeSpentPreference(app)
AppBatteryPreference(app)
diff --git a/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt b/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
new file mode 100644
index 0000000..3a7d50d
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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.app.appinfo
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settingslib.spa.framework.compose.collectAsStateWithLifecycle
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.framework.common.asUser
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+@Composable
+fun InstantAppDomainsPreference(app: ApplicationInfo) {
+ val context = LocalContext.current
+ if (!app.isInstantApp) return
+
+ val presenter = remember { InstantAppDomainsPresenter(context, app) }
+ var openDialog by rememberSaveable { mutableStateOf(false) }
+
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.app_launch_supported_domain_urls_title)
+ override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
+ override val onClick = { openDialog = true }
+ })
+
+ val domainsState = presenter.domainsFlow.collectAsStateWithLifecycle(initialValue = emptySet())
+ if (openDialog) {
+ Dialog(domainsState) {
+ openDialog = false
+ }
+ }
+}
+
+@Composable
+private fun Dialog(domainsState: State<Set<String>>, onDismissRequest: () -> Unit) {
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ confirmButton = {},
+ title = {
+ Text(stringResource(R.string.app_launch_supported_domain_urls_title))
+ },
+ text = {
+ Column {
+ domainsState.value.forEach { domain ->
+ Text(
+ text = domain,
+ modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround),
+ )
+ }
+ }
+ },
+ )
+}
+
+private class InstantAppDomainsPresenter(
+ private val context: Context,
+ private val app: ApplicationInfo,
+) {
+ private val userContext = context.asUser(app.userHandle)
+ private val userPackageManager = userContext.packageManager
+
+ val domainsFlow = flow {
+ emit(Utils.getHandledDomains(userPackageManager, app.packageName))
+ }.flowOn(Dispatchers.IO)
+
+ val summaryFlow = domainsFlow.map { entries ->
+ when (entries.size) {
+ 0 -> context.getString(R.string.domain_urls_summary_none)
+ 1 -> context.getString(R.string.domain_urls_summary_one, entries.first())
+ else -> context.getString(R.string.domain_urls_summary_some, entries.first())
+ }
+ }.flowOn(Dispatchers.IO)
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
new file mode 100644
index 0000000..9782817
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 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.app.appinfo
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.filterToOne
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.testutils.delay
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class InstantAppDomainsPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private lateinit var mockSession: MockitoSession
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ @Before
+ fun setUp() {
+ mockSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(Utils::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ whenever(context.packageManager).thenReturn(packageManager)
+ Mockito.doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
+ mockDomains(emptySet())
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ private fun mockDomains(domains: Set<String>) {
+ whenever(Utils.getHandledDomains(packageManager, PACKAGE_NAME)).thenReturn(domains)
+ }
+
+ @Test
+ fun notInstantApp_notDisplayed() {
+ val app = ApplicationInfo()
+
+ setContent(app)
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun title_displayed() {
+ setContent()
+
+ composeTestRule
+ .onNodeWithText(context.getString(R.string.app_launch_supported_domain_urls_title))
+ .assertIsDisplayed()
+ .assertIsEnabled()
+ }
+
+ @Test
+ fun noDomain() {
+ mockDomains(emptySet())
+
+ setContent()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.domain_urls_summary_none))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun oneDomain() {
+ mockDomains(setOf("abc"))
+
+ setContent()
+
+ composeTestRule.onNodeWithText("Open abc").assertIsDisplayed()
+ }
+
+ @Test
+ fun twoDomains() {
+ mockDomains(setOf("abc", "def"))
+
+ setContent()
+
+ composeTestRule.onNodeWithText("Open abc and other URLs").assertIsDisplayed()
+ }
+
+ @Test
+ fun whenClicked() {
+ mockDomains(setOf("abc", "def"))
+
+ setContent()
+ composeTestRule.onRoot().performClick()
+ composeTestRule.delay()
+
+ assertDialogHasText(context.getString(R.string.app_launch_supported_domain_urls_title))
+ assertDialogHasText("abc")
+ assertDialogHasText("def")
+ }
+
+ private fun assertDialogHasText(text: String) {
+ composeTestRule.onAllNodes(hasAnyAncestor(isDialog()))
+ .filterToOne(hasText(text))
+ .assertIsDisplayed()
+ }
+
+ private fun setContent(app:ApplicationInfo = INSTANT_APP) {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ InstantAppDomainsPreference(app)
+ }
+ }
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val UID = 123
+
+ val INSTANT_APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+ }
+ }
+}