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