1 /*
<lambda>null2  * Copyright 2024 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  *      https://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  */
18 
19 package com.android.healthconnect.controller.permissions.additionalaccess
20 
21 import android.content.Intent.EXTRA_PACKAGE_NAME
22 import android.health.connect.HealthPermissions
23 import android.os.Bundle
24 import android.util.Log
25 import android.view.View
26 import androidx.fragment.app.activityViewModels
27 import com.android.healthconnect.controller.R
28 import com.android.healthconnect.controller.permissions.additionalaccess.PermissionUiState.ALWAYS_ALLOW
29 import com.android.healthconnect.controller.permissions.additionalaccess.PermissionUiState.ASK_EVERY_TIME
30 import com.android.healthconnect.controller.permissions.additionalaccess.PermissionUiState.NOT_DECLARED
31 import com.android.healthconnect.controller.permissions.app.AppPermissionViewModel
32 import com.android.healthconnect.controller.permissions.data.AdditionalPermissionStrings
33 import com.android.healthconnect.controller.permissions.data.HealthPermission
34 import com.android.healthconnect.controller.shared.preference.HealthPreference
35 import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment
36 import com.android.healthconnect.controller.shared.preference.HealthSwitchPreference
37 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter
38 import com.android.healthconnect.controller.utils.logging.AdditionalAccessElement.BACKGROUND_READ_BUTTON
39 import com.android.healthconnect.controller.utils.logging.AdditionalAccessElement.EXERCISE_ROUTES_BUTTON
40 import com.android.healthconnect.controller.utils.logging.AdditionalAccessElement.HISTORY_READ_BUTTON
41 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
42 import com.android.healthconnect.controller.utils.logging.PageName
43 import com.android.healthconnect.controller.utils.pref
44 import com.android.settingslib.widget.AppHeaderPreference
45 import com.android.settingslib.widget.FooterPreference
46 import dagger.hilt.android.AndroidEntryPoint
47 import javax.inject.Inject
48 
49 /** Fragment that contains additional app permission access. */
50 @AndroidEntryPoint(HealthPreferenceFragment::class)
51 class AdditionalAccessFragment : Hilt_AdditionalAccessFragment() {
52 
53     @Inject lateinit var healthConnectLogger: HealthConnectLogger
54     private val permissionsViewModel: AppPermissionViewModel by activityViewModels()
55     private val viewModel: AdditionalAccessViewModel by activityViewModels()
56 
57     private val header: AppHeaderPreference by pref(PREF_APP_HEADER)
58     private val exerciseRoutePref: HealthPreference by pref(KEY_EXERCISE_ROUTES_PERMISSION)
59     private val historicReadPref: HealthSwitchPreference by pref(KEY_HISTORY_READ_PERMISSION)
60     private val backgroundReadPref: HealthSwitchPreference by pref(KEY_BACKGROUND_READ_PERMISSION)
61     private val footerPref: FooterPreference by pref(KEY_FOOTER)
62 
63     private val dateFormatter by lazy { LocalDateTimeFormatter(requireContext()) }
64 
65     lateinit var packageName: String
66 
67     init {
68         setPageName(PageName.ADDITIONAL_ACCESS_PAGE)
69     }
70 
71     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
72         super.onCreatePreferences(savedInstanceState, rootKey)
73         setPreferencesFromResource(R.xml.additional_access_screen, rootKey)
74     }
75 
76     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
77         super.onViewCreated(view, savedInstanceState)
78 
79         val packageNameExtra = requireArguments().getString(EXTRA_PACKAGE_NAME)
80         if (packageNameExtra.isNullOrEmpty()) {
81             Log.e(TAG, "AdditionalAccessFragment is missing $EXTRA_PACKAGE_NAME intent!")
82             requireActivity().finish()
83             return
84         }
85         packageName = packageNameExtra
86 
87         viewModel.additionalAccessState.observe(viewLifecycleOwner) { state ->
88             setupAdditionalPrefs(state)
89         }
90 
91         viewModel.showEnableExerciseEvent.observe(viewLifecycleOwner) { state ->
92             if (state.shouldShowDialog) {
93                 EnableExercisePermissionDialog.createDialog(packageName, state.appName)
94                     .show(childFragmentManager, ENABLE_EXERCISE_DIALOG_TAG)
95             }
96         }
97 
98         permissionsViewModel.appInfo.observe(viewLifecycleOwner) { appMetaData ->
99             header.apply {
100                 icon = appMetaData.icon
101                 title = appMetaData.appName
102             }
103         }
104     }
105 
106     override fun onResume() {
107         super.onResume()
108         viewModel.loadAdditionalAccessPreferences(packageName)
109     }
110 
111     private fun setupExerciseRoutePref(state: PermissionUiState) {
112         exerciseRoutePref.isVisible = state != NOT_DECLARED
113         if (state == NOT_DECLARED) {
114             return
115         }
116 
117         healthConnectLogger.logImpression(EXERCISE_ROUTES_BUTTON)
118         exerciseRoutePref.apply {
119             logName = EXERCISE_ROUTES_BUTTON
120             exerciseRoutePref.setSummary(
121                 when (state) {
122                     ASK_EVERY_TIME -> R.string.route_permissions_ask
123                     ALWAYS_ALLOW -> R.string.route_permissions_always_allow
124                     else -> R.string.route_permissions_deny
125                 })
126             exerciseRoutePref.setOnPreferenceClickListener {
127                 val dialog = ExerciseRoutesPermissionDialogFragment.createDialog(packageName)
128                 dialog.show(childFragmentManager, EXERCISE_ROUTES_DIALOG_TAG)
129                 true
130             }
131         }
132     }
133 
134     private fun maybeShowFooter(state: AdditionalAccessViewModel.State) {
135         val shouldShow = state.showFooter()
136 
137         if (!shouldShow) {
138             footerPref.isVisible = false
139             return
140         }
141 
142         val title =
143             if (state.isAdditionalPermissionDisabled(state.historyReadUIState) &&
144                 state.isAdditionalPermissionDisabled(state.backgroundReadUIState)) {
145                 R.string.additional_access_combined_footer
146             } else if (state.isAdditionalPermissionDisabled(state.backgroundReadUIState)) {
147                 R.string.additional_access_background_footer
148             } else {
149                 R.string.additional_access_history_footer
150             }
151 
152         footerPref.title = getString(title)
153         footerPref.isVisible = true
154     }
155 
156     private fun setupAdditionalPrefs(state: AdditionalAccessViewModel.State) {
157         setupExerciseRoutePref(state.exerciseRoutePermissionUIState)
158         maybeShowFooter(state)
159 
160         if (state.historyReadUIState.isDeclared) {
161             val permStrings =
162                 AdditionalPermissionStrings.fromAdditionalPermission(
163                     HealthPermission.AdditionalPermission.READ_HEALTH_DATA_HISTORY)
164 
165             val dataAccessDate = viewModel.loadAccessDate(packageName)
166             val summary =
167                 if (dataAccessDate != null) {
168                     val formattedDate = dateFormatter.formatLongDate(dataAccessDate)
169                     getString(permStrings.permissionDescription, formattedDate)
170                 } else {
171                     getString(permStrings.permissionDescriptionFallback)
172                 }
173 
174             historicReadPref.isVisible = true
175             healthConnectLogger.logImpression(HISTORY_READ_BUTTON)
176             historicReadPref.isChecked = state.historyReadUIState.isGranted
177             historicReadPref.isEnabled = state.historyReadUIState.isEnabled
178             historicReadPref.summary = summary
179             historicReadPref.logNameActive = HISTORY_READ_BUTTON
180             historicReadPref.logNameInactive = HISTORY_READ_BUTTON
181             historicReadPref.setOnPreferenceChangeListener { _, newValue ->
182                 viewModel.updatePermission(
183                     packageName, HealthPermissions.READ_HEALTH_DATA_HISTORY, newValue as Boolean)
184                 true
185             }
186         }
187 
188         if (state.backgroundReadUIState.isDeclared) {
189             backgroundReadPref.isVisible = true
190             healthConnectLogger.logImpression(BACKGROUND_READ_BUTTON)
191             backgroundReadPref.isChecked = state.backgroundReadUIState.isGranted
192             backgroundReadPref.isEnabled = state.backgroundReadUIState.isEnabled
193             historicReadPref.logNameActive = BACKGROUND_READ_BUTTON
194             historicReadPref.logNameInactive = BACKGROUND_READ_BUTTON
195             backgroundReadPref.setOnPreferenceChangeListener { _, newValue ->
196                 viewModel.updatePermission(
197                     packageName,
198                     HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND,
199                     newValue as Boolean)
200                 true
201             }
202         }
203     }
204 
205     companion object {
206         private const val TAG = "AdditionalAccessFragmen"
207         private const val PREF_APP_HEADER = "manage_app_permission_header"
208         private const val KEY_EXERCISE_ROUTES_PERMISSION = "key_exercise_routes_permission"
209         private const val EXERCISE_ROUTES_DIALOG_TAG = "ExerciseRoutesPermissionDialogFragment"
210         private const val ENABLE_EXERCISE_DIALOG_TAG = "EnableExercisePermissionDialog"
211         private const val KEY_BACKGROUND_READ_PERMISSION = "key_background_read"
212         private const val KEY_HISTORY_READ_PERMISSION = "key_history_read"
213         private const val KEY_FOOTER = "key_additional_access_footer"
214     }
215 }
216