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