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.permissioncontroller.permission.ui.model.v34
18 
19 import android.Manifest
20 import android.app.Activity
21 import android.app.Application
22 import android.content.ActivityNotFoundException
23 import android.content.Context
24 import android.content.Intent
25 import android.content.Intent.ACTION_MANAGE_APP_PERMISSION
26 import android.content.Intent.EXTRA_PACKAGE_NAME
27 import android.content.Intent.EXTRA_PERMISSION_GROUP_NAME
28 import android.content.Intent.EXTRA_USER
29 import android.net.Uri
30 import android.os.Build
31 import android.os.UserHandle
32 import android.util.Log
33 import androidx.annotation.RequiresApi
34 import com.android.permission.safetylabel.DataCategoryConstants.CATEGORY_LOCATION
35 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
36 import com.android.permissioncontroller.R
37 import com.android.permissioncontroller.permission.data.SinglePermGroupPackagesUiInfoLiveData
38 import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
39 import com.android.permissioncontroller.permission.data.v34.AppDataSharingUpdatesLiveData
40 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo
41 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState.PERMS_ALLOWED_ALWAYS
42 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY
43 import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType
44 import com.android.settingslib.HelpUtils
45 import kotlinx.coroutines.Job
46 
47 /** View model for data sharing updates UI. */
48 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
49 class AppDataSharingUpdatesViewModel(app: Application) {
50 
51     private val appDataSharingUpdatesLiveData = AppDataSharingUpdatesLiveData(app)
52     private val locationPermGroupPackagesUiInfoLiveData =
53         SinglePermGroupPackagesUiInfoLiveData[Manifest.permission_group.LOCATION]
54 
55     /** Returns whether UI can provide link to help center */
56     fun canLinkToHelpCenter(context: Context): Boolean {
57         return !getHelpCenterUrlString(context).isNullOrEmpty()
58     }
59 
60     /** Opens the Safety Label Help Center web page. */
61     fun openSafetyLabelsHelpCenterPage(activity: Activity) {
62         if (!canLinkToHelpCenter(activity)) {
63             Log.w(LOG_TAG, "Unable to open help center, no url provided.")
64             return
65         }
66 
67         // Add in some extra locale query parameters
68         val fullUri =
69             HelpUtils.uriWithAddedParameters(activity, Uri.parse(getHelpCenterUrlString(activity)))
70         val intent =
71             Intent(Intent.ACTION_VIEW, fullUri).apply {
72                 setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
73             }
74         try {
75             activity.startActivity(intent)
76         } catch (e: ActivityNotFoundException) {
77             // TODO(b/266755891): show snackbar when help center intent unable to be opened
78             Log.w(LOG_TAG, "Unable to open help center URL.", e)
79         }
80     }
81 
82     /**
83      * Starts the App Permission fragment for location permission for the provided packageName and
84      * userHandle.
85      */
86     fun startAppLocationPermissionPage(
87         activity: Activity,
88         sessionId: Long,
89         packageName: String,
90         userHandle: UserHandle
91     ) {
92         activity.startActivity(
93             Intent(ACTION_MANAGE_APP_PERMISSION).apply {
94                 putExtra(EXTRA_SESSION_ID, sessionId)
95                 putExtra(EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.LOCATION)
96                 putExtra(EXTRA_PACKAGE_NAME, packageName)
97                 putExtra(EXTRA_USER, userHandle)
98             }
99         )
100     }
101 
102     /**
103      * Builds a list of [AppLocationDataSharingUpdateUiInfo], containing all the information
104      * required to render the app data sharing updates.
105      */
106     private fun buildAppLocationDataSharingUpdateUiInfoList():
107         List<AppLocationDataSharingUpdateUiInfo> {
108         val appDataSharingUpdates = appDataSharingUpdatesLiveData.value ?: return listOf()
109 
110         return appDataSharingUpdates
111             .map { appDataSharingUpdate ->
112                 val locationDataSharingUpdate =
113                     appDataSharingUpdate.categorySharingUpdates[CATEGORY_LOCATION]
114 
115                 if (locationDataSharingUpdate == null) {
116                     emptyList()
117                 } else {
118                     val users =
119                         locationPermGroupPackagesUiInfoLiveData.getUsersWithPermGrantedForApp(
120                             appDataSharingUpdate.packageName
121                         )
122                     users.map { user ->
123                         // For each user profile under the current user, display one entry.
124                         AppLocationDataSharingUpdateUiInfo(
125                             appDataSharingUpdate.packageName,
126                             user,
127                             locationDataSharingUpdate
128                         )
129                     }
130                 }
131             }
132             .flatten()
133     }
134 
135     private fun getHelpCenterUrlString(context: Context): String? {
136         return context.getString(R.string.data_sharing_help_center_link)
137     }
138 
139     private fun SinglePermGroupPackagesUiInfoLiveData.getUsersWithPermGrantedForApp(
140         packageName: String
141     ): List<UserHandle> {
142         return value
143             ?.filter {
144                 packageToPermInfoEntry: Map.Entry<Pair<String, UserHandle>, AppPermGroupUiInfo> ->
145                 val appPermGroupUiInfo = packageToPermInfoEntry.value
146 
147                 appPermGroupUiInfo.isPermissionGranted()
148             }
149             ?.keys
150             ?.filter { packageUser: Pair<String, UserHandle> -> packageUser.first == packageName }
151             ?.map { packageUser: Pair<String, UserHandle> -> packageUser.second }
152             ?: listOf()
153     }
154 
155     private fun AppPermGroupUiInfo.isPermissionGranted() =
156         permGrantState == PERMS_ALLOWED_ALWAYS || permGrantState == PERMS_ALLOWED_FOREGROUND_ONLY
157 
158     /** All the information necessary to display an app's data sharing update in the UI. */
159     data class AppLocationDataSharingUpdateUiInfo(
160         val packageName: String,
161         val userHandle: UserHandle,
162         val dataSharingUpdateType: DataSharingUpdateType
163     )
164 
165     /** LiveData for all data sharing updates to be displayed in the UI. */
166     val appLocationDataSharingUpdateUiInfoLiveData =
167         object : SmartAsyncMediatorLiveData<List<AppLocationDataSharingUpdateUiInfo>>() {
168 
169             init {
170                 addSource(appDataSharingUpdatesLiveData) { onUpdate() }
171                 addSource(locationPermGroupPackagesUiInfoLiveData) { onUpdate() }
172             }
173 
174             override suspend fun loadDataAndPostValue(job: Job) {
175                 if (locationPermGroupPackagesUiInfoLiveData.isStale) {
176                     return
177                 }
178 
179                 if (appDataSharingUpdatesLiveData.isStale) {
180                     return
181                 }
182 
183                 postValue(buildAppLocationDataSharingUpdateUiInfoList())
184             }
185         }
186 
187     /** Companion object for [AppDataSharingUpdatesViewModel]. */
188     companion object {
189         private val LOG_TAG = AppDataSharingUpdatesViewModel::class.java.simpleName
190     }
191 }
192