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