• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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 package com.android.healthconnect.controller.home
17 
18 import android.content.Context
19 import android.content.Intent
20 import android.os.Bundle
21 import android.view.View
22 import androidx.core.os.bundleOf
23 import androidx.fragment.app.activityViewModels
24 import androidx.fragment.app.viewModels
25 import androidx.navigation.fragment.findNavController
26 import androidx.preference.Preference
27 import androidx.preference.PreferenceGroup
28 import com.android.healthconnect.controller.HealthFitnessUiStatsLog.*
29 import com.android.healthconnect.controller.R
30 import com.android.healthconnect.controller.dataentries.formatters.DurationFormatter
31 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.maybeShowWhatsNewDialog
32 import com.android.healthconnect.controller.migration.MigrationViewModel
33 import com.android.healthconnect.controller.migration.api.MigrationState
34 import com.android.healthconnect.controller.permissions.shared.Constants
35 import com.android.healthconnect.controller.recentaccess.RecentAccessEntry
36 import com.android.healthconnect.controller.recentaccess.RecentAccessPreference
37 import com.android.healthconnect.controller.recentaccess.RecentAccessViewModel
38 import com.android.healthconnect.controller.recentaccess.RecentAccessViewModel.RecentAccessState
39 import com.android.healthconnect.controller.shared.app.ConnectedAppMetadata
40 import com.android.healthconnect.controller.shared.app.ConnectedAppStatus
41 import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder
42 import com.android.healthconnect.controller.shared.preference.BannerPreference
43 import com.android.healthconnect.controller.shared.preference.HealthPreference
44 import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment
45 import com.android.healthconnect.controller.utils.AttributeResolver
46 import com.android.healthconnect.controller.utils.logging.ErrorPageElement
47 import com.android.healthconnect.controller.utils.logging.HomePageElement
48 import com.android.healthconnect.controller.utils.logging.PageName
49 import dagger.hilt.android.AndroidEntryPoint
50 import java.time.Duration
51 
52 /** Home fragment for Health Connect. */
53 @AndroidEntryPoint(HealthPreferenceFragment::class)
54 class HomeFragment : Hilt_HomeFragment() {
55 
56     companion object {
57         private const val DATA_AND_ACCESS_PREFERENCE_KEY = "data_and_access"
58         private const val RECENT_ACCESS_PREFERENCE_KEY = "recent_access"
59         private const val CONNECTED_APPS_PREFERENCE_KEY = "connected_apps"
60         private const val MIGRATION_BANNER_PREFERENCE_KEY = "migration_banner"
61 
62         @JvmStatic fun newInstance() = HomeFragment()
63     }
64 
65     init {
66         this.setPageName(PageName.HOME_PAGE)
67     }
68 
69     private val recentAccessViewModel: RecentAccessViewModel by viewModels()
70     private val homeFragmentViewModel: HomeFragmentViewModel by viewModels()
71     private val migrationViewModel: MigrationViewModel by activityViewModels()
72 
73     private val mDataAndAccessPreference: HealthPreference? by lazy {
74         preferenceScreen.findPreference(DATA_AND_ACCESS_PREFERENCE_KEY)
75     }
76 
77     private val mRecentAccessPreference: PreferenceGroup? by lazy {
78         preferenceScreen.findPreference(RECENT_ACCESS_PREFERENCE_KEY)
79     }
80 
81     private val mConnectedAppsPreference: HealthPreference? by lazy {
82         preferenceScreen.findPreference(CONNECTED_APPS_PREFERENCE_KEY)
83     }
84 
85     private lateinit var migrationBannerSummary: String
86     private var migrationBanner: BannerPreference? = null
87 
88     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
89         super.onCreatePreferences(savedInstanceState, rootKey)
90         setPreferencesFromResource(R.xml.home_preference_screen, rootKey)
91         mDataAndAccessPreference?.logName = HomePageElement.DATA_AND_ACCESS_BUTTON
92         mDataAndAccessPreference?.setOnPreferenceClickListener {
93             findNavController().navigate(R.id.action_homeFragment_to_healthDataCategoriesFragment)
94             true
95         }
96         mConnectedAppsPreference?.logName = HomePageElement.APP_PERMISSIONS_BUTTON
97         mConnectedAppsPreference?.setOnPreferenceClickListener {
98             findNavController().navigate(R.id.action_homeFragment_to_connectedAppsFragment)
99             true
100         }
101 
102         migrationBannerSummary = getString(R.string.resume_migration_banner_description_fallback)
103         migrationBanner = getMigrationBanner()
104     }
105 
106     override fun onResume() {
107         super.onResume()
108         recentAccessViewModel.loadRecentAccessApps(maxNumEntries = 3)
109         homeFragmentViewModel.loadConnectedApps()
110     }
111 
112     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
113         super.onViewCreated(view, savedInstanceState)
114 
115         recentAccessViewModel.loadRecentAccessApps(maxNumEntries = 3)
116         recentAccessViewModel.recentAccessApps.observe(viewLifecycleOwner) { recentAppsState ->
117             when (recentAppsState) {
118                 is RecentAccessState.WithData -> {
119                     updateRecentApps(recentAppsState.recentAccessEntries)
120                 }
121                 else -> {
122                     updateRecentApps(emptyList())
123                 }
124             }
125         }
126         homeFragmentViewModel.connectedApps.observe(viewLifecycleOwner) { connectedApps ->
127             updateConnectedApps(connectedApps)
128         }
129         migrationViewModel.migrationState.observe(viewLifecycleOwner) { migrationState ->
130             when (migrationState) {
131                 is MigrationViewModel.MigrationFragmentState.WithData -> {
132                     showMigrationState(migrationState.migrationState)
133                 }
134                 else -> {
135                     // do nothing
136                 }
137             }
138         }
139     }
140 
141     private fun updateMigrationBannerText(duration: Duration) {
142         val formattedDuration =
143             DurationFormatter.formatDurationDaysOrHours(requireContext(), duration)
144         migrationBannerSummary =
145             getString(R.string.resume_migration_banner_description, formattedDuration)
146         migrationBanner?.summary = migrationBannerSummary
147     }
148 
149     private fun showMigrationState(migrationState: MigrationState) {
150         preferenceScreen.removePreferenceRecursively(MIGRATION_BANNER_PREFERENCE_KEY)
151 
152         when (migrationState) {
153             MigrationState.ALLOWED_PAUSED,
154             MigrationState.ALLOWED_NOT_STARTED,
155             MigrationState.MODULE_UPGRADE_REQUIRED,
156             MigrationState.APP_UPGRADE_REQUIRED -> {
157                 migrationBanner = getMigrationBanner()
158                 preferenceScreen.addPreference(migrationBanner)
159             }
160             MigrationState.COMPLETE -> {
161                 maybeShowWhatsNewDialog(requireContext())
162             }
163             MigrationState.ALLOWED_ERROR -> {
164                 maybeShowMigrationNotCompleteDialog()
165             }
166             else -> {
167                 // show nothing
168             }
169         }
170     }
171 
172     private fun maybeShowMigrationNotCompleteDialog() {
173         val sharedPreference =
174             requireActivity().getSharedPreferences("USER_ACTIVITY_TRACKER", Context.MODE_PRIVATE)
175         val dialogSeen =
176             sharedPreference.getBoolean(
177                 getString(R.string.migration_not_complete_dialog_seen), false)
178 
179         if (!dialogSeen) {
180             AlertDialogBuilder(this)
181                 .setLogName(ErrorPageElement.UNKNOWN_ELEMENT)
182                 .setTitle(R.string.migration_not_complete_dialog_title)
183                 .setMessage(R.string.migration_not_complete_dialog_content)
184                 .setCancelable(false)
185                 .setNegativeButton(
186                     R.string.migration_whats_new_dialog_button, ErrorPageElement.UNKNOWN_ELEMENT) {
187                         _,
188                         _ ->
189                         sharedPreference.edit().apply {
190                             putBoolean(getString(R.string.migration_not_complete_dialog_seen), true)
191                             apply()
192                         }
193                     }
194                 .create()
195                 .show()
196         }
197     }
198 
199     private fun getMigrationBanner(): BannerPreference {
200         return BannerPreference(requireContext()).also {
201             it.setButton(resources.getString(R.string.resume_migration_banner_button))
202             it.title = resources.getString(R.string.resume_migration_banner_title)
203             it.key = MIGRATION_BANNER_PREFERENCE_KEY
204             it.summary = migrationBannerSummary
205             it.setIcon(R.drawable.ic_settings_alert)
206             it.setButtonOnClickListener {
207                 findNavController().navigate(R.id.action_homeFragment_to_migrationActivity)
208             }
209             it.order = 1
210         }
211     }
212 
213     private fun updateConnectedApps(connectedApps: List<ConnectedAppMetadata>) {
214         val connectedAppsGroup = connectedApps.groupBy { it.status }
215         val numAllowedApps = connectedAppsGroup[ConnectedAppStatus.ALLOWED].orEmpty().size
216         val numNotAllowedApps = connectedAppsGroup[ConnectedAppStatus.DENIED].orEmpty().size
217         val numTotalApps = numAllowedApps + numNotAllowedApps
218 
219         if (numTotalApps == 0) {
220             mConnectedAppsPreference?.summary =
221                 getString(R.string.connected_apps_button_no_permissions_subtitle)
222         } else if (numAllowedApps == 1 && numAllowedApps == numTotalApps) {
223             mConnectedAppsPreference?.summary =
224                 getString(
225                     R.string.connected_apps_one_app_connected_subtitle, numAllowedApps.toString())
226         } else if (numAllowedApps == numTotalApps) {
227             mConnectedAppsPreference?.summary =
228                 getString(
229                     R.string.connected_apps_all_apps_connected_subtitle, numAllowedApps.toString())
230         } else {
231             mConnectedAppsPreference?.summary =
232                 getString(
233                     R.string.connected_apps_button_subtitle,
234                     numAllowedApps.toString(),
235                     numTotalApps.toString())
236         }
237     }
238 
239     private fun updateRecentApps(recentAppsList: List<RecentAccessEntry>) {
240         mRecentAccessPreference?.removeAll()
241 
242         if (recentAppsList.isEmpty()) {
243             mRecentAccessPreference?.addPreference(
244                 Preference(requireContext()).also { it.setSummary(R.string.no_recent_access) })
245         } else {
246             recentAppsList.forEach { recentApp ->
247                 val newRecentAccessPreference =
248                     RecentAccessPreference(requireContext(), recentApp, false).also { newPreference
249                         ->
250                         if (!recentApp.isInactive) {
251                             newPreference.setOnPreferenceClickListener {
252                                 findNavController()
253                                     .navigate(
254                                         R.id.action_homeFragment_to_connectedAppFragment,
255                                         bundleOf(
256                                             Intent.EXTRA_PACKAGE_NAME to
257                                                 recentApp.metadata.packageName,
258                                             Constants.EXTRA_APP_NAME to recentApp.metadata.appName))
259                                 true
260                             }
261                         }
262                     }
263                 mRecentAccessPreference?.addPreference(newRecentAccessPreference)
264             }
265             val seeAllPreference =
266                 HealthPreference(requireContext()).also {
267                     it.setTitle(R.string.show_recent_access_entries_button_title)
268                     it.setIcon(AttributeResolver.getResource(requireContext(), R.attr.seeAllIcon))
269                     it.logName = HomePageElement.SEE_ALL_RECENT_ACCESS_BUTTON
270                 }
271             seeAllPreference.setOnPreferenceClickListener {
272                 findNavController().navigate(R.id.action_homeFragment_to_recentAccessFragment)
273                 true
274             }
275             mRecentAccessPreference?.addPreference(seeAllPreference)
276         }
277     }
278 }
279