1 /*
<lambda>null2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.spa.app.specialaccess
18 
19 import android.app.AppOpsManager
20 import android.content.Context
21 import android.content.pm.ActivityInfo
22 import android.content.pm.ApplicationInfo
23 import android.content.pm.PackageInfo
24 import android.content.pm.PackageManager.GET_ACTIVITIES
25 import android.content.pm.PackageManager.PackageInfoFlags
26 import android.util.Log
27 import androidx.compose.runtime.Composable
28 import com.android.settings.R
29 import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle
30 import com.android.settingslib.spaprivileged.model.app.AppOps
31 import com.android.settingslib.spaprivileged.model.app.AppOpsController
32 import com.android.settingslib.spaprivileged.model.app.AppRecord
33 import com.android.settingslib.spaprivileged.model.app.installed
34 import com.android.settingslib.spaprivileged.model.app.userId
35 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
36 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.flow.map
40 
41 object PictureInPictureListProvider : TogglePermissionAppListProvider {
42     override val permissionType = "PictureInPicture"
43     override fun createModel(context: Context) = PictureInPictureListModel(context)
44 }
45 
46 data class PictureInPictureRecord(
47     override val app: ApplicationInfo,
48     val isSupport: Boolean,
49     val appOpsController: AppOpsController,
50 ) : AppRecord
51 
52 class PictureInPictureListModel(private val context: Context) :
53     TogglePermissionAppListModel<PictureInPictureRecord> {
54     override val pageTitleResId = R.string.picture_in_picture_title
55     override val switchTitleResId = R.string.picture_in_picture_app_detail_switch
56     override val footerResId = R.string.picture_in_picture_app_detail_summary
57     override val enhancedConfirmationKey: String = AppOpsManager.OPSTR_PICTURE_IN_PICTURE
58 
59     private val packageManager = context.packageManager
60 
transformnull61     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
62         userIdFlow.map(::getPictureInPicturePackages)
63             .combine(appListFlow) { pictureInPicturePackages, appList ->
64                 appList.map { app ->
65                     createPictureInPictureRecord(
66                         app = app,
67                         isSupport = app.packageName in pictureInPicturePackages,
68                     )
69                 }
70             }
71 
transformItemnull72     override fun transformItem(app: ApplicationInfo) = createPictureInPictureRecord(
73         app = app,
74         isSupport = app.installed &&
75             getPackageAndActivityInfo(app)?.supportsPictureInPicture() == true,
76     )
77 
78     private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) =
79         PictureInPictureRecord(
80             app = app,
81             isSupport = isSupport,
82             appOpsController = AppOpsController(context = context, app = app, appOps = APP_OPS),
83         )
84 
85     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<PictureInPictureRecord>>) =
86         recordListFlow.map { recordList -> recordList.filter { it.isSupport } }
87 
88     @Composable
isAllowednull89     override fun isAllowed(record: PictureInPictureRecord) =
90         record.appOpsController.isAllowed.collectAsCallbackWithLifecycle()
91 
92     override fun isChangeable(record: PictureInPictureRecord) = record.isSupport
93 
94     override fun setAllowed(record: PictureInPictureRecord, newAllowed: Boolean) {
95         record.appOpsController.setAllowed(newAllowed)
96     }
97 
getPictureInPicturePackagesnull98     private fun getPictureInPicturePackages(userId: Int): Set<String> =
99         getPackageAndActivityInfoList(userId)
100             .filter { it.supportsPictureInPicture() }
<lambda>null101             .map { it.packageName }
102             .toSet()
103 
getPackageAndActivityInfonull104     private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try {
105         packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
106     } catch (e: Exception) {
107         // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause
108         // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture
109         // packages, we need to catch the exception to alleviate the impact before PackageManager
110         // fixing this issue or provide a better api.
111         Log.e(TAG, "Exception while getPackageInfoAsUser", e)
112         null
113     }
114 
getPackageAndActivityInfoListnull115     private fun getPackageAndActivityInfoList(userId: Int): List<PackageInfo> = try {
116         packageManager.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId)
117     } catch (e: Exception) {
118         // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause
119         // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture
120         // packages, we need to catch the exception to alleviate the impact before PackageManager
121         // fixing this issue or provide a better api.
122         Log.e(TAG, "Exception while getInstalledPackagesAsUser", e)
123         emptyList()
124     }
125 
126     companion object {
127         private const val TAG = "PictureInPictureListModel"
128 
129         private val APP_OPS = AppOps(AppOpsManager.OP_PICTURE_IN_PICTURE)
130 
PackageInfonull131         private fun PackageInfo.supportsPictureInPicture() =
132             activities?.any(ActivityInfo::supportsPictureInPicture) ?: false
133 
134         private val GET_ACTIVITIES_FLAGS = PackageInfoFlags.of(GET_ACTIVITIES.toLong())
135     }
136 }
137