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.OP_PICTURE_IN_PICTURE 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 androidx.compose.runtime.livedata.observeAsState 29 import com.android.settings.R 30 import com.android.settingslib.spaprivileged.model.app.AppOpsController 31 import com.android.settingslib.spaprivileged.model.app.AppRecord 32 import com.android.settingslib.spaprivileged.model.app.installed 33 import com.android.settingslib.spaprivileged.model.app.userId 34 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel 35 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.combine 38 import kotlinx.coroutines.flow.map 39 40 object PictureInPictureListProvider : TogglePermissionAppListProvider { 41 override val permissionType = "PictureInPicture" 42 override fun createModel(context: Context) = PictureInPictureListModel(context) 43 } 44 45 data class PictureInPictureRecord( 46 override val app: ApplicationInfo, 47 val isSupport: Boolean, 48 val appOpsController: AppOpsController, 49 ) : AppRecord 50 51 class PictureInPictureListModel(private val context: Context) : 52 TogglePermissionAppListModel<PictureInPictureRecord> { 53 override val pageTitleResId = R.string.picture_in_picture_title 54 override val switchTitleResId = R.string.picture_in_picture_app_detail_switch 55 override val footerResId = R.string.picture_in_picture_app_detail_summary 56 57 private val packageManager = context.packageManager 58 transformnull59 override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = 60 userIdFlow.map(::getPictureInPicturePackages) 61 .combine(appListFlow) { pictureInPicturePackages, appList -> 62 appList.map { app -> 63 createPictureInPictureRecord( 64 app = app, 65 isSupport = app.packageName in pictureInPicturePackages, 66 ) 67 } 68 } 69 transformItemnull70 override fun transformItem(app: ApplicationInfo) = createPictureInPictureRecord( 71 app = app, 72 isSupport = app.installed && 73 getPackageAndActivityInfo(app)?.supportsPictureInPicture() == true, 74 ) 75 76 private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) = 77 PictureInPictureRecord( 78 app = app, 79 isSupport = isSupport, 80 appOpsController = 81 AppOpsController( 82 context = context, 83 app = app, 84 op = OP_PICTURE_IN_PICTURE, 85 ), 86 ) 87 88 override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<PictureInPictureRecord>>) = 89 recordListFlow.map { recordList -> recordList.filter { it.isSupport } } 90 91 @Composable isAllowednull92 override fun isAllowed(record: PictureInPictureRecord) = 93 record.appOpsController.isAllowed.observeAsState() 94 95 override fun isChangeable(record: PictureInPictureRecord) = record.isSupport 96 97 override fun setAllowed(record: PictureInPictureRecord, newAllowed: Boolean) { 98 record.appOpsController.setAllowed(newAllowed) 99 } 100 getPictureInPicturePackagesnull101 private fun getPictureInPicturePackages(userId: Int): Set<String> = 102 getPackageAndActivityInfoList(userId) 103 .filter { it.supportsPictureInPicture() } <lambda>null104 .map { it.packageName } 105 .toSet() 106 getPackageAndActivityInfonull107 private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try { 108 packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) 109 } catch (e: Exception) { 110 // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause 111 // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture 112 // packages, we need to catch the exception to alleviate the impact before PackageManager 113 // fixing this issue or provide a better api. 114 Log.e(TAG, "Exception while getPackageInfoAsUser", e) 115 null 116 } 117 getPackageAndActivityInfoListnull118 private fun getPackageAndActivityInfoList(userId: Int): List<PackageInfo> = try { 119 packageManager.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId) 120 } catch (e: Exception) { 121 // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause 122 // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture 123 // packages, we need to catch the exception to alleviate the impact before PackageManager 124 // fixing this issue or provide a better api. 125 Log.e(TAG, "Exception while getInstalledPackagesAsUser", e) 126 emptyList() 127 } 128 129 companion object { 130 private const val TAG = "PictureInPictureListModel" 131 PackageInfonull132 private fun PackageInfo.supportsPictureInPicture() = 133 activities?.any(ActivityInfo::supportsPictureInPicture) ?: false 134 135 private val GET_ACTIVITIES_FLAGS = PackageInfoFlags.of(GET_ACTIVITIES.toLong()) 136 } 137 } 138