1 /* <lambda>null2 * Copyright (C) 2023 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.wear 18 19 import android.Manifest 20 import android.app.Activity 21 import android.content.Intent 22 import android.os.Bundle 23 import android.os.UserHandle 24 import android.view.LayoutInflater 25 import android.view.View 26 import android.view.ViewGroup 27 import androidx.annotation.StringRes 28 import androidx.compose.ui.platform.ComposeView 29 import androidx.core.os.BundleCompat 30 import androidx.fragment.app.Fragment 31 import androidx.lifecycle.ViewModelProvider 32 import com.android.permissioncontroller.Constants 33 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID 34 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW 35 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS 36 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND 37 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME 38 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY 39 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND 40 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION 41 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION 42 import com.android.permissioncontroller.R 43 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler 44 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED 45 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN 46 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS 47 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY 48 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity 49 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME 50 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel 51 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType 52 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest 53 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ConfirmDialogShowingFragment 54 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory 55 import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs 56 import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel 57 import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModelFactory 58 import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs 59 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme 60 import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel 61 import com.android.settingslib.RestrictedLockUtils 62 63 /** 64 * Show and manage a single permission group for an app. 65 * 66 * <p>Allows the user to control whether the app is granted the permission 67 * 68 * <p> 69 * Based on AppPermissionFragment in handheld code. 70 */ 71 class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment { 72 73 private lateinit var confirmDialogViewModel: AppPermissionConfirmDialogViewModel 74 75 companion object { 76 private const val GRANT_CATEGORY = "grant_category" 77 78 /** 79 * Create a bundle with the arguments needed by this fragment 80 * 81 * @param packageName The name of the package 82 * @param permName The name of the permission whose group this fragment is for (optional) 83 * @param groupName The name of the permission group (required if permName not specified) 84 * @param userHandle The user of the app permission group 85 * @param caller The name of the fragment we called from 86 * @param sessionId The current session ID 87 * @param grantCategory The grant status of this app permission group. Used to initially set 88 * the button state 89 * @return A bundle with all of the args placed 90 */ 91 @JvmStatic 92 fun createArgs( 93 packageName: String?, 94 permName: String?, 95 groupName: String?, 96 userHandle: UserHandle?, 97 caller: String?, 98 sessionId: Long, 99 grantCategory: String? 100 ): Bundle { 101 val arguments = Bundle() 102 arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName) 103 if (groupName == null) { 104 arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName) 105 } else { 106 arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName) 107 } 108 arguments.putParcelable(Intent.EXTRA_USER, userHandle) 109 arguments.putString(EXTRA_CALLER_NAME, caller) 110 arguments.putLong(EXTRA_SESSION_ID, sessionId) 111 arguments.putString(GRANT_CATEGORY, grantCategory) 112 return arguments 113 } 114 } 115 116 override fun onCreateView( 117 inflater: LayoutInflater, 118 container: ViewGroup?, 119 savedInstanceState: Bundle? 120 ): View? { 121 val activity = requireActivity() 122 val packageName = 123 arguments?.getString(Intent.EXTRA_PACKAGE_NAME) 124 ?: throw RuntimeException("Package name must not be null.") 125 val permGroupName = 126 arguments?.getString(Intent.EXTRA_PERMISSION_GROUP_NAME) 127 ?: arguments?.getString(Intent.EXTRA_PERMISSION_NAME) 128 ?: throw RuntimeException("Permission name must not be null.") 129 130 val isStorageGroup = permGroupName == Manifest.permission_group.STORAGE 131 132 val user = 133 arguments?.let { 134 BundleCompat.getParcelable(it, Intent.EXTRA_USER, UserHandle::class.java) 135 } 136 ?: UserHandle.SYSTEM 137 val permGroupLabel = getPermGroupLabel(activity, permGroupName).toString() 138 139 val sessionId = arguments?.getLong(EXTRA_SESSION_ID) ?: Constants.INVALID_SESSION_ID 140 141 val factory = 142 AppPermissionViewModelFactory( 143 activity.getApplication(), 144 packageName, 145 permGroupName, 146 user, 147 sessionId 148 ) 149 val viewModel = ViewModelProvider(this, factory).get(AppPermissionViewModel::class.java) 150 confirmDialogViewModel = 151 ViewModelProvider(this, AppPermissionConfirmDialogViewModelFactory()) 152 .get(AppPermissionConfirmDialogViewModel::class.java) 153 154 @Suppress("ktlint:standard:max-line-length") 155 val onLocationSwitchChanged: (Boolean) -> Unit = { checked -> 156 run { 157 val changeRequest = 158 if (checked) { 159 ChangeRequest.GRANT_FINE_LOCATION 160 } else { 161 ChangeRequest.REVOKE_FINE_LOCATION 162 } 163 val buttonClicked = 164 if (checked) { 165 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION 166 } else { 167 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION 168 } 169 viewModel.requestChange(false, this, this, changeRequest, buttonClicked) 170 } 171 } 172 val onGrantedStateChanged: (ButtonType, Boolean) -> Unit = { buttonType, checked -> 173 run { 174 if (!checked) { 175 return@run 176 } 177 val param = getGrantedStateChangeParam(buttonType) 178 if (!isStorageGroup || !param.requiresCustomStorageBehavior) { 179 viewModel.requestChange( 180 param.setOneTime, 181 this, 182 this, 183 param.request, 184 param.buttonClickAction 185 ) 186 } else { 187 showConfirmDialog( 188 ChangeRequest.GRANT_ALL_FILE_ACCESS, 189 R.string.special_file_access_dialog, 190 -1, 191 false 192 ) 193 } 194 setResult(param.result, permGroupName) 195 } 196 } 197 val onFooterClicked: (RestrictedLockUtils.EnforcedAdmin) -> Unit = { admin -> 198 run { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(requireContext(), admin) } 199 } 200 val onConfirmDialogOkButtonClick: (ConfirmDialogArgs) -> Unit = { args -> 201 run { 202 if (args.changeRequest == ChangeRequest.GRANT_ALL_FILE_ACCESS) { 203 viewModel.setAllFilesAccess(true) 204 viewModel.requestChange( 205 false, 206 this, 207 this, 208 ChangeRequest.GRANT_BOTH, 209 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW 210 ) 211 } else { 212 viewModel.onDenyAnyWay(args.changeRequest, args.buttonPressed, args.oneTime) 213 } 214 confirmDialogViewModel.showConfirmDialogLiveData.value = false 215 } 216 } 217 val onConfirmDialogCancelButtonClick: () -> Unit = { 218 confirmDialogViewModel.showConfirmDialogLiveData.value = false 219 } 220 val onAdvancedConfirmDialogOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit = { args -> 221 run { 222 viewModel.requestChange( 223 args.setOneTime!!, 224 this, 225 this, 226 args.changeRequest!!, 227 args.buttonClicked!! 228 ) 229 confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = false 230 } 231 } 232 val onAdvancedConfirmDialogCancelButtonClick: () -> Unit = { 233 confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = false 234 } 235 236 return ComposeView(activity).apply { 237 setContent { 238 WearPermissionTheme { 239 WearAppPermissionScreen( 240 permGroupLabel, 241 viewModel, 242 confirmDialogViewModel, 243 onLocationSwitchChanged, 244 onGrantedStateChanged, 245 onFooterClicked, 246 onConfirmDialogOkButtonClick, 247 onConfirmDialogCancelButtonClick, 248 onAdvancedConfirmDialogOkButtonClick, 249 onAdvancedConfirmDialogCancelButtonClick 250 ) 251 } 252 } 253 } 254 } 255 256 override fun showConfirmDialog( 257 changeRequest: ChangeRequest, 258 @StringRes messageId: Int, 259 buttonPressed: Int, 260 oneTime: Boolean 261 ) { 262 confirmDialogViewModel.confirmDialogArgs = 263 ConfirmDialogArgs( 264 messageId = messageId, 265 changeRequest = changeRequest, 266 buttonPressed = buttonPressed, 267 oneTime = oneTime 268 ) 269 confirmDialogViewModel.showConfirmDialogLiveData.value = true 270 } 271 272 override fun showAdvancedConfirmDialog(args: AdvancedConfirmDialogArgs) { 273 confirmDialogViewModel.advancedConfirmDialogArgs = args 274 confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = true 275 } 276 277 private fun setResult(@GrantPermissionsViewHandler.Result result: Int, permGroupName: String) { 278 val intent: Intent = 279 Intent() 280 .putExtra( 281 ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED, 282 permGroupName 283 ) 284 .putExtra(ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT, result) 285 requireActivity().setResult(Activity.RESULT_OK, intent) 286 } 287 288 fun getGrantedStateChangeParam(buttonType: ButtonType) = 289 when (buttonType) { 290 ButtonType.ALLOW -> 291 GrantedStateChangeParam( 292 false, 293 ChangeRequest.GRANT_FOREGROUND, 294 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW, 295 GRANTED_ALWAYS, 296 false 297 ) 298 ButtonType.ALLOW_ALWAYS -> 299 GrantedStateChangeParam( 300 false, 301 ChangeRequest.GRANT_BOTH, 302 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS, 303 GRANTED_ALWAYS, 304 true 305 ) 306 ButtonType.ALLOW_FOREGROUND -> 307 GrantedStateChangeParam( 308 false, 309 ChangeRequest.GRANT_FOREGROUND_ONLY, 310 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND, 311 GRANTED_FOREGROUND_ONLY, 312 true 313 ) 314 ButtonType.ASK -> 315 GrantedStateChangeParam( 316 true, 317 ChangeRequest.REVOKE_BOTH, 318 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME, 319 DENIED, 320 false 321 ) 322 ButtonType.DENY -> 323 GrantedStateChangeParam( 324 false, 325 ChangeRequest.REVOKE_BOTH, 326 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY, 327 DENIED_DO_NOT_ASK_AGAIN, 328 false 329 ) 330 ButtonType.DENY_FOREGROUND -> 331 GrantedStateChangeParam( 332 false, 333 ChangeRequest.REVOKE_FOREGROUND, 334 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND, 335 DENIED_DO_NOT_ASK_AGAIN, 336 false 337 ) 338 else -> throw RuntimeException("Wrong button type: $buttonType") 339 } 340 } 341 342 data class GrantedStateChangeParam( 343 val setOneTime: Boolean, 344 val request: ChangeRequest, 345 val buttonClickAction: Int, 346 val result: Int, 347 val requiresCustomStorageBehavior: Boolean 348 ) 349