1 /*
<lambda>null2  * Copyright (C) 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  *      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.healthconnect.controller.backuprestore
18 
19 import android.os.Bundle
20 import android.view.View
21 import android.widget.Toast
22 import androidx.fragment.app.viewModels
23 import androidx.navigation.fragment.findNavController
24 import androidx.preference.PreferenceGroup
25 import com.android.healthconnect.controller.R
26 import com.android.healthconnect.controller.exportimport.ExportStatusPreference
27 import com.android.healthconnect.controller.exportimport.api.ExportFrequency
28 import com.android.healthconnect.controller.exportimport.api.ExportSettings
29 import com.android.healthconnect.controller.exportimport.api.ExportSettingsViewModel
30 import com.android.healthconnect.controller.exportimport.api.ExportStatusViewModel
31 import com.android.healthconnect.controller.exportimport.api.ImportStatusViewModel
32 import com.android.healthconnect.controller.exportimport.api.ImportUiState
33 import com.android.healthconnect.controller.exportimport.api.ImportUiStatus
34 import com.android.healthconnect.controller.exportimport.api.ScheduledExportUiState
35 import com.android.healthconnect.controller.exportimport.api.ScheduledExportUiStatus
36 import com.android.healthconnect.controller.shared.preference.BannerPreference
37 import com.android.healthconnect.controller.shared.preference.HealthPreference
38 import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment
39 import com.android.healthconnect.controller.utils.AttributeResolver
40 import com.android.healthconnect.controller.utils.DeviceInfoUtilsImpl
41 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter
42 import com.android.healthconnect.controller.utils.logging.ErrorPageElement
43 import com.android.healthconnect.controller.utils.pref
44 import com.android.settingslib.widget.FooterPreference
45 import dagger.hilt.android.AndroidEntryPoint
46 
47 /** Fragment displaying backup and restore settings. */
48 @AndroidEntryPoint(HealthPreferenceFragment::class)
49 class BackupAndRestoreSettingsFragment : Hilt_BackupAndRestoreSettingsFragment() {
50     // TODO: b/330169060 - Add proper logging for the backup and restore settings fragment.
51 
52     companion object {
53         const val SCHEDULED_EXPORT_PREFERENCE_KEY = "scheduled_export"
54         const val IMPORT_DATA_PREFERENCE_KEY = "import_data"
55         const val EXPORT_IMPORT_SETTINGS_CATEGORY_PREFERENCE_KEY = "settings_category"
56         const val IMPORT_ERROR_BANNER_KEY = "import_error_banner"
57         const val IMPORT_ERROR_BANNER_ORDER = 0
58         const val PREVIOUS_EXPORT_STATUS_ORDER = 2
59     }
60 
61     private val exportSettingsViewModel: ExportSettingsViewModel by viewModels()
62     private val exportStatusViewModel: ExportStatusViewModel by viewModels()
63     private val importStatusViewModel: ImportStatusViewModel by viewModels()
64 
65     private val scheduledExportPreference: HealthPreference? by lazy {
66         preferenceScreen.findPreference(SCHEDULED_EXPORT_PREFERENCE_KEY)
67     }
68 
69     private val importDataPreference: HealthPreference? by lazy {
70         preferenceScreen.findPreference(IMPORT_DATA_PREFERENCE_KEY)
71     }
72 
73     private val settingsCategory: PreferenceGroup? by lazy {
74         preferenceScreen.findPreference(EXPORT_IMPORT_SETTINGS_CATEGORY_PREFERENCE_KEY)
75     }
76 
77     private val dateFormatter: LocalDateTimeFormatter by lazy {
78         LocalDateTimeFormatter(requireContext())
79     }
80 
81     private val footerPreference: FooterPreference by pref("backup_restore_footer")
82 
83     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
84         super.onCreatePreferences(savedInstanceState, rootKey)
85         setPreferencesFromResource(R.xml.backup_and_restore_settings_screen, rootKey)
86 
87         footerPreference.setLearnMoreText(getString(R.string.backup_and_restore_footer_link_text))
88         footerPreference.setLearnMoreAction {
89             DeviceInfoUtilsImpl().openHCGetStartedLink(requireActivity())
90         }
91 
92         scheduledExportPreference?.setOnPreferenceClickListener {
93             findNavController()
94                 .navigate(R.id.action_backupAndRestoreSettingsFragment_to_exportSetupActivity)
95             true
96         }
97 
98         importDataPreference?.setOnPreferenceClickListener {
99             findNavController()
100                 .navigate(R.id.action_backupAndRestoreSettingsFragment_to_importFlowActivity)
101             true
102         }
103     }
104 
105     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
106         super.onViewCreated(view, savedInstanceState)
107 
108         importStatusViewModel.storedImportStatus.observe(viewLifecycleOwner) { importUiStatus ->
109             when (importUiStatus) {
110                 is ImportUiStatus.WithData -> {
111                     maybeShowImportErrorBanner(importUiStatus.importUiState)
112                 }
113                 else -> {
114                     // Do nothing.
115                 }
116             }
117         }
118 
119         exportStatusViewModel.storedScheduledExportStatus.observe(viewLifecycleOwner) {
120             scheduledExportUiStatus ->
121             when (scheduledExportUiStatus) {
122                 is ScheduledExportUiStatus.WithData -> {
123                     maybeShowPreviousExportStatus(scheduledExportUiStatus.scheduledExportUiState)
124                 }
125                 else -> {
126                     // do nothing
127                 }
128             }
129         }
130 
131         exportSettingsViewModel.storedExportSettings.observe(viewLifecycleOwner) { exportSettings ->
132             when (exportSettings) {
133                 is ExportSettings.WithData -> {
134                     val frequency = exportSettings.frequency
135                     if (frequency == ExportFrequency.EXPORT_FREQUENCY_NEVER) {
136                         scheduledExportPreference?.setOnPreferenceClickListener {
137                             findNavController()
138                                 .navigate(
139                                     R.id
140                                         .action_backupAndRestoreSettingsFragment_to_exportSetupActivity)
141                             true
142                         }
143                     } else {
144                         scheduledExportPreference?.setOnPreferenceClickListener {
145                             findNavController()
146                                 .navigate(
147                                     R.id
148                                         .action_backupAndRestoreSettingsFragment_to_scheduledExportFragment)
149                             true
150                         }
151                     }
152                     scheduledExportPreference?.summary = buildSummary(frequency)
153                 }
154                 is ExportSettings.LoadingFailed ->
155                     Toast.makeText(activity, R.string.default_error, Toast.LENGTH_LONG).show()
156                 else -> {}
157             }
158         }
159     }
160 
161     override fun onResume() {
162         super.onResume()
163         exportSettingsViewModel.loadExportSettings()
164     }
165 
166     private fun buildSummary(frequency: ExportFrequency): String {
167         val on = getString(R.string.automatic_export_on)
168         val automaticExportStatusId = R.string.automatic_export_status
169         return when (frequency) {
170             ExportFrequency.EXPORT_FREQUENCY_NEVER -> getString(R.string.automatic_export_off)
171             ExportFrequency.EXPORT_FREQUENCY_DAILY ->
172                 getString(automaticExportStatusId, on, getString(R.string.frequency_daily))
173             ExportFrequency.EXPORT_FREQUENCY_WEEKLY ->
174                 getString(automaticExportStatusId, on, getString(R.string.frequency_weekly))
175             ExportFrequency.EXPORT_FREQUENCY_MONTHLY ->
176                 getString(automaticExportStatusId, on, getString(R.string.frequency_monthly))
177         }
178     }
179 
180     private fun maybeShowPreviousExportStatus(scheduledExportUiState: ScheduledExportUiState) {
181         val lastSuccessfulExportTime = scheduledExportUiState.lastSuccessfulExportTime
182         if (lastSuccessfulExportTime != null) {
183             val lastExportTime =
184                 getString(
185                     R.string.last_export_time,
186                     dateFormatter.formatLongDate(lastSuccessfulExportTime))
187             settingsCategory?.addPreference(
188                 ExportStatusPreference(requireContext(), lastExportTime).also {
189                     it.order = PREVIOUS_EXPORT_STATUS_ORDER
190                 })
191         }
192     }
193 
194     private fun maybeShowImportErrorBanner(importUiState: ImportUiState) {
195         if (importUiState.dataImportError ==
196             ImportUiState.DataImportError.DATA_IMPORT_ERROR_WRONG_FILE) {
197             preferenceScreen.addPreference(
198                 BannerPreference(requireContext(), ErrorPageElement.UNKNOWN_ELEMENT).also {
199                     it.setPrimaryButton(
200                         getString(R.string.import_wrong_file_error_banner_button),
201                         ErrorPageElement.UNKNOWN_ELEMENT)
202                     it.title = getString(R.string.import_wrong_file_error_banner_title)
203                     it.key = IMPORT_ERROR_BANNER_KEY
204                     it.summary = getString(R.string.import_wrong_file_error_banner_summary)
205                     it.icon =
206                         AttributeResolver.getNullableDrawable(requireContext(), R.attr.warningIcon)
207                     it.setPrimaryButtonOnClickListener {
208                         findNavController()
209                             .navigate(
210                                 R.id.action_backupAndRestoreSettingsFragment_to_importFlowActivity)
211                     }
212                     it.order = IMPORT_ERROR_BANNER_ORDER
213                 })
214         }
215     }
216 }
217