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.Manifest 20 import android.app.AlarmManager 21 import android.app.AppOpsManager 22 import android.app.compat.CompatChanges 23 import android.app.settings.SettingsEnums 24 import android.content.Context 25 import android.content.pm.ApplicationInfo 26 import android.os.PowerExemptionManager 27 import androidx.compose.runtime.Composable 28 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory 29 import com.android.settingslib.R 30 import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle 31 import com.android.settingslib.spaprivileged.model.app.AppOps 32 import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController 33 import com.android.settingslib.spaprivileged.model.app.AppRecord 34 import com.android.settingslib.spaprivileged.model.app.IPackageManagers 35 import com.android.settingslib.spaprivileged.model.app.PackageManagers 36 import com.android.settingslib.spaprivileged.model.app.userHandle 37 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel 38 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.map 42 43 object AlarmsAndRemindersAppListProvider : TogglePermissionAppListProvider { 44 override val permissionType = "AlarmsAndReminders" 45 override fun createModel(context: Context) = AlarmsAndRemindersAppListModel(context) 46 } 47 48 data class AlarmsAndRemindersAppRecord( 49 override val app: ApplicationInfo, 50 val isTrumped: Boolean, 51 val isChangeable: Boolean, 52 val controller: AppOpsPermissionController, 53 ) : AppRecord 54 55 class AlarmsAndRemindersAppListModel( 56 private val context: Context, 57 private val packageManagers: IPackageManagers = PackageManagers, 58 ) : TogglePermissionAppListModel<AlarmsAndRemindersAppRecord> { 59 override val pageTitleResId = R.string.alarms_and_reminders_title 60 override val switchTitleResId = R.string.alarms_and_reminders_switch_title 61 override val footerResId = R.string.alarms_and_reminders_footer_title 62 override val enhancedConfirmationKey: String = AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM 63 transformnull64 override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = 65 userIdFlow.map { userId -> 66 PackageManagers.getAppOpPermissionPackages(userId, PERMISSION) 67 }.combine(appListFlow) { packageNames, appList -> appnull68 appList.map { app -> 69 createRecord(app = app, hasRequestPermission = app.packageName in packageNames) 70 } 71 } 72 <lambda>null73 override fun transformItem(app: ApplicationInfo) = with(packageManagers) { 74 createRecord(app = app, hasRequestPermission = app.hasRequestPermission(PERMISSION)) 75 } 76 filternull77 override fun filter( 78 userIdFlow: Flow<Int>, 79 recordListFlow: Flow<List<AlarmsAndRemindersAppRecord>>, 80 ) = recordListFlow.map { recordList -> 81 recordList.filter { it.isChangeable } 82 } 83 84 @Composable isAllowednull85 override fun isAllowed(record: AlarmsAndRemindersAppRecord): () -> Boolean? = when { 86 record.isTrumped -> ({ true }) 87 else -> record.controller.isAllowedFlow.collectAsCallbackWithLifecycle() 88 } 89 isChangeablenull90 override fun isChangeable(record: AlarmsAndRemindersAppRecord) = record.isChangeable 91 92 override fun setAllowed(record: AlarmsAndRemindersAppRecord, newAllowed: Boolean) { 93 record.controller.setAllowed(newAllowed) 94 logPermissionChange(newAllowed) 95 } 96 logPermissionChangenull97 private fun logPermissionChange(newAllowed: Boolean) { 98 featureFactory.metricsFeatureProvider.action( 99 SettingsEnums.PAGE_UNKNOWN, 100 SettingsEnums.ACTION_ALARMS_AND_REMINDERS_TOGGLE, 101 SettingsEnums.ALARMS_AND_REMINDERS, 102 "", 103 if (newAllowed) 1 else 0 104 ) 105 } 106 createRecordnull107 private fun createRecord( 108 app: ApplicationInfo, 109 hasRequestPermission: Boolean, 110 ): AlarmsAndRemindersAppRecord { 111 val hasRequestSeaPermission = hasRequestPermission && app.isSeaEnabled() 112 val isTrumped = hasRequestSeaPermission && app.isTrumped() 113 return AlarmsAndRemindersAppRecord( 114 app = app, 115 isTrumped = isTrumped, 116 isChangeable = hasRequestPermission && !isTrumped, 117 controller = AppOpsPermissionController( 118 context = context, 119 app = app, 120 appOps = APP_OPS, 121 permission = PERMISSION, 122 ), 123 ) 124 } 125 126 /** 127 * If trumped, this app will be treated as allowed, and the toggle is not changeable by user. 128 */ <lambda>null129 private fun ApplicationInfo.isTrumped(): Boolean = with(packageManagers) { 130 val hasRequestUseExactAlarm = hasRequestPermission(Manifest.permission.USE_EXACT_ALARM) && 131 CompatChanges.isChangeEnabled( 132 AlarmManager.ENABLE_USE_EXACT_ALARM, packageName, userHandle, 133 ) 134 val isPowerAllowListed = context.getSystemService(PowerExemptionManager::class.java) 135 ?.isAllowListed(packageName, true) ?: false 136 return hasRequestUseExactAlarm || isPowerAllowListed 137 } 138 139 companion object { 140 private val APP_OPS = AppOps( 141 op = AppOpsManager.OP_SCHEDULE_EXACT_ALARM, 142 setModeByUid = true, 143 ) 144 145 private const val PERMISSION: String = Manifest.permission.SCHEDULE_EXACT_ALARM 146 147 /** Checks whether [Manifest.permission.SCHEDULE_EXACT_ALARM] is enabled. */ ApplicationInfonull148 private fun ApplicationInfo.isSeaEnabled(): Boolean = 149 CompatChanges.isChangeEnabled( 150 AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, packageName, userHandle, 151 ) 152 } 153 } 154