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