• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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