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 */ 18 19 /** 20 * Copyright (C) 2022 The Android Open Source Project 21 * 22 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 23 * in compliance with the License. You may obtain a copy of the License at 24 * 25 * ``` 26 * http://www.apache.org/licenses/LICENSE-2.0 27 * ``` 28 * 29 * Unless required by applicable law or agreed to in writing, software distributed under the License 30 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 31 * or implied. See the License for the specific language governing permissions and limitations under 32 * the License. 33 */ 34 package com.android.healthconnect.controller.permissions.connectedapps 35 36 import android.content.Intent.EXTRA_PACKAGE_NAME 37 import android.os.Bundle 38 import android.view.View 39 import androidx.annotation.StringRes 40 import androidx.core.os.bundleOf 41 import androidx.fragment.app.viewModels 42 import androidx.navigation.fragment.findNavController 43 import androidx.preference.Preference 44 import androidx.preference.PreferenceGroup 45 import com.android.healthconnect.controller.R 46 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.maybeShowWhatsNewDialog 47 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.showDataRestoreInProgressDialog 48 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.showMigrationInProgressDialog 49 import com.android.healthconnect.controller.migration.MigrationViewModel 50 import com.android.healthconnect.controller.migration.api.MigrationRestoreState 51 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiState 52 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.MigrationUiState 53 import com.android.healthconnect.controller.shared.Constants.EXTRA_APP_NAME 54 import com.android.healthconnect.controller.shared.app.ConnectedAppMetadata 55 import com.android.healthconnect.controller.shared.app.ConnectedAppStatus.ALLOWED 56 import com.android.healthconnect.controller.shared.app.ConnectedAppStatus.DENIED 57 import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment 58 import com.android.healthconnect.controller.utils.dismissLoadingDialog 59 import com.android.healthconnect.controller.utils.logging.AppPermissionsElement 60 import com.android.healthconnect.controller.utils.logging.PageName 61 import com.android.healthconnect.controller.utils.showLoadingDialog 62 import com.android.settingslib.widget.AppPreference 63 import dagger.hilt.android.AndroidEntryPoint 64 65 /** 66 * Fragment to show allowed and denied apps for health permissions. It is used as an entry point 67 * from PermissionController. 68 */ 69 @AndroidEntryPoint(HealthPreferenceFragment::class) 70 class SettingsManagePermissionFragment : Hilt_SettingsManagePermissionFragment() { 71 72 companion object { 73 const val ALLOWED_APPS_GROUP = "allowed_apps" 74 const val DENIED_APPS_GROUP = "denied_apps" 75 } 76 77 init { 78 this.setPageName(PageName.SETTINGS_MANAGE_PERMISSIONS_PAGE) 79 } 80 81 private val allowedAppsGroup: PreferenceGroup? by lazy { 82 preferenceScreen.findPreference(ALLOWED_APPS_GROUP) 83 } 84 85 private val deniedAppsGroup: PreferenceGroup? by lazy { 86 preferenceScreen.findPreference(DENIED_APPS_GROUP) 87 } 88 89 private val viewModel: ConnectedAppsViewModel by viewModels() 90 private val migrationViewModel: MigrationViewModel by viewModels() 91 92 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 93 super.onCreatePreferences(savedInstanceState, rootKey) 94 setPreferencesFromResource(R.xml.settings_manage_permission_screen, rootKey) 95 } 96 97 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 98 super.onViewCreated(view, savedInstanceState) 99 viewModel.connectedApps.observe(viewLifecycleOwner) { connectedApps -> 100 val connectedAppsGroup = connectedApps.groupBy { it.status } 101 updateAllowedApps(connectedAppsGroup[ALLOWED].orEmpty()) 102 updateDeniedApps(connectedAppsGroup[DENIED].orEmpty()) 103 } 104 viewModel.disconnectAllState.observe(viewLifecycleOwner) { state -> 105 when (state) { 106 is ConnectedAppsViewModel.DisconnectAllState.Loading -> { 107 showLoadingDialog() 108 } 109 else -> { 110 dismissLoadingDialog() 111 } 112 } 113 } 114 migrationViewModel.migrationState.observe(viewLifecycleOwner) { migrationState -> 115 when (migrationState) { 116 is MigrationViewModel.MigrationFragmentState.WithData -> { 117 maybeShowMigrationDialog(migrationState.migrationRestoreState) 118 } 119 else -> { 120 // do nothing 121 } 122 } 123 } 124 } 125 126 private fun maybeShowMigrationDialog(migrationRestoreState: MigrationRestoreState) { 127 val (migrationUiState, dataRestoreUiState, dataErrorState) = migrationRestoreState 128 129 if (dataRestoreUiState == DataRestoreUiState.IN_PROGRESS) { 130 showDataRestoreInProgressDialog(requireContext()) { _, _ -> requireActivity().finish() } 131 } else if (migrationUiState == MigrationUiState.IN_PROGRESS) { 132 showMigrationInProgressDialog( 133 requireContext(), 134 getString(R.string.migration_in_progress_permissions_dialog_content_apps), 135 ) { _, _ -> 136 requireActivity().finish() 137 } 138 } else if (migrationUiState == MigrationUiState.COMPLETE) { 139 maybeShowWhatsNewDialog(requireContext()) 140 } 141 } 142 143 override fun onResume() { 144 super.onResume() 145 viewModel.loadConnectedApps() 146 } 147 148 private fun updateAllowedApps(appsList: List<ConnectedAppMetadata>) { 149 allowedAppsGroup?.removeAll() 150 if (appsList.isEmpty()) { 151 allowedAppsGroup?.addPreference(getNoAppsPreference(R.string.no_apps_allowed)) 152 } else { 153 appsList.forEach { app -> allowedAppsGroup?.addPreference(getAppPreference(app)) } 154 } 155 } 156 157 private fun updateDeniedApps(appsList: List<ConnectedAppMetadata>) { 158 deniedAppsGroup?.removeAll() 159 160 if (appsList.isEmpty()) { 161 deniedAppsGroup?.addPreference(getNoAppsPreference(R.string.no_apps_denied)) 162 } else { 163 appsList.forEach { app -> deniedAppsGroup?.addPreference(getAppPreference(app)) } 164 } 165 } 166 167 private fun getNoAppsPreference(@StringRes res: Int): Preference { 168 return Preference(requireContext()).also { 169 it.setTitle(res) 170 it.isSelectable = false 171 } 172 } 173 174 private fun getAppPreference(app: ConnectedAppMetadata): AppPreference { 175 return HealthAppPreference(requireContext(), app.appMetadata).also { 176 if (app.status == ALLOWED) { 177 it.logName = AppPermissionsElement.CONNECTED_APP_BUTTON 178 } else if (app.status == DENIED) { 179 it.logName = AppPermissionsElement.NOT_CONNECTED_APP_BUTTON 180 } 181 if (app.healthUsageLastAccess != null) { 182 it.setSummary(R.string.app_perms_content_provider_24h) 183 } else { 184 it.summary = null 185 } 186 it.setOnPreferenceClickListener { 187 findNavController() 188 .navigate( 189 R.id.action_settingsManagePermission_to_settingsManageAppPermissions, 190 bundleOf( 191 EXTRA_PACKAGE_NAME to app.appMetadata.packageName, 192 EXTRA_APP_NAME to app.appMetadata.appName)) 193 true 194 } 195 } 196 } 197 } 198