• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.permissioncontroller.permission.ui.model.v33
18 
19 import android.app.Application
20 import android.content.Intent
21 import android.graphics.drawable.Drawable
22 import android.icu.lang.UCharacter
23 import android.os.Build
24 import android.os.UserHandle
25 import android.text.BidiFormatter
26 import androidx.annotation.RequiresApi
27 import androidx.lifecycle.ViewModel
28 import androidx.lifecycle.ViewModelProvider
29 import com.android.permissioncontroller.DumpableLog
30 import com.android.permissioncontroller.R
31 import com.android.permissioncontroller.permission.utils.PermissionMapping
32 import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
33 import com.android.permissioncontroller.permission.data.UserPackageInfosLiveData
34 import com.android.permissioncontroller.permission.data.v33.PermissionDecision
35 import com.android.permissioncontroller.permission.data.v33.RecentPermissionDecisionsLiveData
36 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
37 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
38 import com.android.permissioncontroller.permission.ui.auto.AutoReviewPermissionDecisionsFragment
39 import com.android.permissioncontroller.permission.utils.KotlinUtils
40 import com.android.permissioncontroller.permission.utils.StringUtils
41 import kotlinx.coroutines.Job
42 import java.util.concurrent.TimeUnit
43 
44 /** Viewmodel for [ReviewPermissionDecisionsFragment] */
45 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
46 class ReviewPermissionDecisionsViewModel(val app: Application, val user: UserHandle) : ViewModel() {
47 
48     val LOG_TAG = "ReviewPermissionDecisionsViewModel"
49 
50     private val recentPermissionsLiveData = RecentPermissionDecisionsLiveData()
51     private val userPackageInfosLiveData = UserPackageInfosLiveData[user]
52 
53     val recentPermissionDecisionsLiveData = object
54         : SmartAsyncMediatorLiveData<List<PermissionDecision>>(
55         alwaysUpdateOnActive = false
56     ) {
57 
58         init {
<lambda>null59             addSource(recentPermissionsLiveData) {
60                 onUpdate()
61             }
62 
<lambda>null63             addSource(userPackageInfosLiveData) {
64                 onUpdate()
65             }
66         }
67 
loadDataAndPostValuenull68         override suspend fun loadDataAndPostValue(job: Job) {
69             if (!recentPermissionsLiveData.isInitialized ||
70                 !userPackageInfosLiveData.isInitialized) {
71                 return
72             }
73 
74             // create package info lookup map for performance
75             val packageToLightPackageInfo: MutableMap<String, LightPackageInfo> = mutableMapOf()
76             for (lightPackageInfo in userPackageInfosLiveData.value!!) {
77                 packageToLightPackageInfo[lightPackageInfo.packageName] = lightPackageInfo
78             }
79 
80             // verify that permission state is still correct. Will also filter out any apps that
81             // were uninstalled
82             val decisionsToReview: MutableList<PermissionDecision> = mutableListOf()
83             for (recentDecision in recentPermissionsLiveData.value!!) {
84                 val lightPackageInfo = packageToLightPackageInfo[recentDecision.packageName]
85                 if (lightPackageInfo == null) {
86                     DumpableLog.e(LOG_TAG, "Package $recentDecision.packageName " +
87                         "is no longer installed")
88                     continue
89                 }
90                 val grantedGroups: List<String?> = lightPackageInfo.grantedPermissions.map {
91                     PermissionMapping.getGroupOfPermission(
92                         app.packageManager.getPermissionInfo(it, /* flags= */ 0))
93                 }
94                 val currentlyGranted = grantedGroups.contains(recentDecision.permissionGroupName)
95                 if (currentlyGranted && recentDecision.isGranted) {
96                     decisionsToReview.add(recentDecision)
97                 } else if (!currentlyGranted && !recentDecision.isGranted) {
98                     decisionsToReview.add(recentDecision)
99                 } else {
100                     // It's okay for this to happen - the state could change due to role changes,
101                     // app hibernation, or other non-user-driven actions.
102                     DumpableLog.d(LOG_TAG,
103                         "Permission decision grant state (${recentDecision.isGranted}) " +
104                             "for ${recentDecision.packageName} access to " +
105                             "${recentDecision.permissionGroupName} does not match current " +
106                             "grant state $currentlyGranted")
107                 }
108             }
109 
110             postValue(decisionsToReview)
111         }
112     }
113 
getAppIconnull114     fun getAppIcon(packageName: String): Drawable? {
115         return KotlinUtils.getBadgedPackageIcon(app, packageName, user)
116     }
117 
createPreferenceTitlenull118     fun createPreferenceTitle(permissionDecision: PermissionDecision): String {
119         val packageLabel = BidiFormatter.getInstance().unicodeWrap(
120             KotlinUtils.getPackageLabel(app, permissionDecision.packageName, user))
121         val permissionGroupLabel = KotlinUtils.getPermGroupLabel(app,
122             permissionDecision.permissionGroupName).toString()
123         return if (permissionDecision.isGranted) {
124             app.getString(R.string.granted_permission_decision, packageLabel,
125                 UCharacter.toLowerCase(permissionGroupLabel))
126         } else {
127             app.getString(R.string.denied_permission_decision, packageLabel,
128                 UCharacter.toLowerCase(permissionGroupLabel))
129         }
130     }
131 
createManageAppPermissionIntentnull132     fun createManageAppPermissionIntent(permissionDecision: PermissionDecision): Intent {
133         return Intent(Intent.ACTION_MANAGE_APP_PERMISSION).apply {
134             putExtra(Intent.EXTRA_PACKAGE_NAME, permissionDecision.packageName)
135             putExtra(Intent.EXTRA_PERMISSION_NAME, permissionDecision.permissionGroupName)
136             putExtra(Intent.EXTRA_USER, user)
137             putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME,
138                 AutoReviewPermissionDecisionsFragment::class.java.name)
139         }
140     }
141 
createSummaryTextnull142     fun createSummaryText(permissionDecision: PermissionDecision): String {
143         val diff = System.currentTimeMillis() - permissionDecision.eventTime
144         val daysAgo = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS).toInt()
145         return StringUtils.getIcuPluralsString(app, R.string.days_ago, daysAgo)
146     }
147 }
148 
149 /**
150  * Factory for a [ReviewPermissionDecisionsViewModel]
151  */
152 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
153 class ReviewPermissionDecisionsViewModelFactory(val app: Application, val user: UserHandle) :
154     ViewModelProvider.Factory {
155 
createnull156     override fun <T : ViewModel> create(modelClass: Class<T>): T {
157         @Suppress("UNCHECKED_CAST")
158         return ReviewPermissionDecisionsViewModel(app, user) as T
159     }
160 }