Add PictureInPicture to Spa

Bug: 235727273
Test: Manual with Settings App
Change-Id: I6ef15dd49fd74ba2d59a8e55c0b7a6c2cd1cd928
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 24bd192..5578631 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -133,7 +133,7 @@
 import com.android.settings.notification.NotificationBackend;
 import com.android.settings.notification.app.AppNotificationSettings;
 import com.android.settings.spa.SpaActivity;
-import com.android.settings.spa.app.InstallUnknownAppsListProvider;
+import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider;
 import com.android.settings.widget.LoadingViewController;
 import com.android.settings.wifi.AppStateChangeWifiStateBridge;
 import com.android.settings.wifi.ChangeWifiStateDetails;
diff --git a/src/com/android/settings/spa/SpaEnvironment.kt b/src/com/android/settings/spa/SpaEnvironment.kt
index 7da253a..489c8a8 100644
--- a/src/com/android/settings/spa/SpaEnvironment.kt
+++ b/src/com/android/settings/spa/SpaEnvironment.kt
@@ -16,7 +16,10 @@
 
 package com.android.settings.spa
 
-import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.AppsMainPageProvider
+import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
+import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
 import com.android.settings.spa.home.HomePageProvider
 import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
@@ -29,11 +32,16 @@
     val settingsPageProviders: SettingsPageProviderRepository by
     lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
         val togglePermissionAppListTemplate = TogglePermissionAppListTemplate(
-            allProviders = listOf(InstallUnknownAppsListProvider),
+            allProviders = listOf(
+                InstallUnknownAppsListProvider,
+                PictureInPictureListProvider,
+            ),
         )
         SettingsPageProviderRepository(
             allPageProviders = listOf(
                 HomePageProvider,
+                AppsMainPageProvider,
+                SpecialAppAccessPageProvider,
                 NotificationMainPageProvider,
                 AppListNotificationsPageProvider,
             ) + togglePermissionAppListTemplate.createPageProviders(),
diff --git a/src/com/android/settings/spa/app/AppsMain.kt b/src/com/android/settings/spa/app/AppsMain.kt
new file mode 100644
index 0000000..b3ee12f
--- /dev/null
+++ b/src/com/android/settings/spa/app/AppsMain.kt
@@ -0,0 +1,74 @@
+/*
+ * 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
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Apps
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+
+object AppsMainPageProvider : SettingsPageProvider {
+    override val name = "AppsMain"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        AppsMain()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = stringResource(R.string.apps_dashboard_title)
+            override val summary =
+                stringResource(R.string.app_and_notification_dashboard_summary).toState()
+            override val onClick = navigator(name)
+            override val icon = @Composable {
+                SettingsIcon(imageVector = Icons.Outlined.Apps)
+            }
+        })
+    }
+
+    fun buildInjectEntry() =
+        SettingsEntryBuilder.createInject(SettingsPage.create(name)).setIsAllowSearch(false)
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = SettingsPage.create(name, parameter, arguments)
+        return listOf(
+            SpecialAppAccessPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
+@Composable
+private fun AppsMain() {
+    RegularScaffold(title = stringResource(R.string.apps_dashboard_title)) {
+        SpecialAppAccessPageProvider.EntryItem()
+    }
+}
diff --git a/src/com/android/settings/spa/app/InstallUnknownApps.kt b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
similarity index 98%
rename from src/com/android/settings/spa/app/InstallUnknownApps.kt
rename to src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
index a09fda2..77c0f68 100644
--- a/src/com/android/settings/spa/app/InstallUnknownApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settings.spa.app
+package com.android.settings.spa.app.specialaccess
 
 import android.Manifest
 import android.app.AppGlobals
diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
new file mode 100644
index 0000000..0c56e56
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.specialaccess
+
+import android.app.AppOpsManager.OP_PICTURE_IN_PICTURE
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.content.pm.PackageManager.PackageInfoFlags
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.model.app.AppOpsController
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object PictureInPictureListProvider : TogglePermissionAppListProvider {
+    override val permissionType = "PictureInPicture"
+    override fun createModel(context: Context) = PictureInPictureListModel(context)
+}
+
+data class PictureInPictureRecord(
+    override val app: ApplicationInfo,
+    val isSupport: Boolean,
+    val appOpsController: AppOpsController,
+) : AppRecord
+
+class PictureInPictureListModel(private val context: Context)
+    : TogglePermissionAppListModel<PictureInPictureRecord> {
+    override val pageTitleResId = R.string.picture_in_picture_title
+    override val switchTitleResId = R.string.picture_in_picture_app_detail_switch
+    override val footerResId = R.string.picture_in_picture_app_detail_summary
+
+    private val packageManager = context.packageManager
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        userIdFlow.map(::getPictureInPicturePackages)
+            .combine(appListFlow) { pictureInPicturePackages, appList ->
+                appList.map { app ->
+                    createPictureInPictureRecord(
+                        app = app,
+                        isSupport = app.packageName in pictureInPicturePackages,
+                    )
+                }
+            }
+
+    override fun transformItem(app: ApplicationInfo): PictureInPictureRecord {
+        val packageInfo =
+            packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
+        return createPictureInPictureRecord(
+            app = app,
+            isSupport = packageInfo.supportsPictureInPicture(),
+        )
+    }
+
+    private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) =
+        PictureInPictureRecord(
+            app = app,
+            isSupport = isSupport,
+            appOpsController = AppOpsController(
+                context = context,
+                app = app,
+                op = OP_PICTURE_IN_PICTURE,
+            ),
+        )
+
+    override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<PictureInPictureRecord>>) =
+        recordListFlow.map { recordList ->
+            recordList.filter { it.isSupport }
+        }
+
+    @Composable
+    override fun isAllowed(record: PictureInPictureRecord) =
+        record.appOpsController.isAllowed.observeAsState()
+
+    override fun isChangeable(record: PictureInPictureRecord) = record.isSupport
+
+    override fun setAllowed(record: PictureInPictureRecord, newAllowed: Boolean) {
+        record.appOpsController.setAllowed(newAllowed)
+    }
+
+    private fun getPictureInPicturePackages(userId: Int): Set<String> =
+        packageManager.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId)
+            .filter { it.supportsPictureInPicture() }
+            .map { it.packageName }
+            .toSet()
+
+    companion object {
+        private fun PackageInfo.supportsPictureInPicture() =
+            activities?.any(ActivityInfo::supportsPictureInPicture) ?: false
+
+        private val GET_ACTIVITIES_FLAGS = PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+    }
+}
diff --git a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
new file mode 100644
index 0000000..f7b1f82
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.specialaccess
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+object SpecialAppAccessPageProvider : SettingsPageProvider {
+    override val name = "SpecialAppAccess"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SpecialAppAccessPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = stringResource(R.string.special_access)
+            override val onClick = navigator(name)
+        })
+    }
+
+    fun buildInjectEntry() =
+        SettingsEntryBuilder.createInject(SettingsPage.create(name)).setIsAllowSearch(false)
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = SettingsPage.create(name, parameter, arguments)
+        return listOf(
+            PictureInPictureListProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            InstallUnknownAppsListProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
+@Composable
+private fun SpecialAppAccessPage() {
+    RegularScaffold(title = stringResource(R.string.special_access)) {
+        PictureInPictureListProvider.EntryItem()
+        InstallUnknownAppsListProvider.EntryItem()
+    }
+}
diff --git a/src/com/android/settings/spa/home/HomePage.kt b/src/com/android/settings/spa/home/HomePage.kt
index 3b8fb84..424d91f 100644
--- a/src/com/android/settings/spa/home/HomePage.kt
+++ b/src/com/android/settings/spa/home/HomePage.kt
@@ -23,8 +23,10 @@
 import androidx.compose.ui.res.stringResource
 import com.android.settings.R
 import com.android.settings.spa.SpaEnvironment
-import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.AppsMainPageProvider
 import com.android.settings.spa.notification.NotificationMainPageProvider
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
 
@@ -35,12 +37,19 @@
     override fun Page(arguments: Bundle?) {
         HomePage()
     }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = SettingsPage.create(name, parameter, arguments)
+        return listOf(
+            AppsMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+        )
+    }
 }
 
 @Composable
 private fun HomePage() {
     HomeScaffold(title = stringResource(R.string.settings_label)) {
-        InstallUnknownAppsListProvider.EntryItem()
+        AppsMainPageProvider.EntryItem()
         NotificationMainPageProvider.EntryItem()
 
         /**