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