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