Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f8d383b authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Add PictureInPicture to Spa

Bug: 235727273
Test: Manual with Settings App
Change-Id: I6ef15dd49fd74ba2d59a8e55c0b7a6c2cd1cd928
parent dba6ba52
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -133,7 +133,7 @@ import com.android.settings.notification.ConfigureNotificationSettings;
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;
+10 −2
Original line number Diff line number Diff line
@@ -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 @@ object SpaEnvironment {
    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(),
+74 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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
+115 −0
Original line number Diff line number Diff line
/*
 * 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())
    }
}
Loading