• 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 
17 package com.android.healthconnect.controller.recentaccess
18 
19 import android.content.Intent
20 import android.os.Bundle
21 import android.view.LayoutInflater
22 import android.view.View
23 import android.view.ViewGroup
24 import android.widget.FrameLayout
25 import android.widget.Toast
26 import androidx.core.os.bundleOf
27 import androidx.core.view.isVisible
28 import androidx.fragment.app.viewModels
29 import androidx.navigation.fragment.findNavController
30 import androidx.preference.Preference
31 import androidx.preference.PreferenceGroup
32 import androidx.recyclerview.widget.RecyclerView
33 import com.android.healthconnect.controller.R
34 import com.android.healthconnect.controller.recentaccess.RecentAccessViewModel.RecentAccessState
35 import com.android.healthconnect.controller.shared.Constants
36 import com.android.healthconnect.controller.shared.app.AppPermissionsType
37 import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment
38 import com.android.healthconnect.controller.utils.TimeSource
39 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
40 import com.android.healthconnect.controller.utils.logging.PageName
41 import com.android.healthconnect.controller.utils.logging.RecentAccessElement
42 import com.android.healthconnect.controller.utils.pref
43 import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
44 import dagger.hilt.android.AndroidEntryPoint
45 import javax.inject.Inject
46 
47 /** Recent access fragment showing a timeline of apps that have recently accessed Health Connect. */
48 @AndroidEntryPoint(HealthPreferenceFragment::class)
49 class RecentAccessFragment : Hilt_RecentAccessFragment() {
50 
51     companion object {
52         private const val RECENT_ACCESS_TODAY_KEY = "recent_access_today"
53         private const val RECENT_ACCESS_YESTERDAY_KEY = "recent_access_yesterday"
54         private const val RECENT_ACCESS_NO_DATA_KEY = "no_data"
55     }
56 
57     init {
58         this.setPageName(PageName.RECENT_ACCESS_PAGE)
59     }
60 
61     @Inject lateinit var logger: HealthConnectLogger
62     @Inject lateinit var timeSource: TimeSource
63 
64     private val viewModel: RecentAccessViewModel by viewModels()
65     private lateinit var contentParent: FrameLayout
66     private lateinit var fab: ExtendedFloatingActionButton
67     private var recyclerView: RecyclerView? = null
68 
69     private val mRecentAccessTodayPreferenceGroup: PreferenceGroup by pref(RECENT_ACCESS_TODAY_KEY)
70 
71     private val mRecentAccessYesterdayPreferenceGroup: PreferenceGroup by
72         pref(RECENT_ACCESS_YESTERDAY_KEY)
73 
74     private val mRecentAccessNoDataPreference: Preference by pref(RECENT_ACCESS_NO_DATA_KEY)
75 
76     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
77         super.onCreatePreferences(savedInstanceState, rootKey)
78         setPreferencesFromResource(R.xml.recent_access_preference_screen, rootKey)
79     }
80 
81     override fun onCreateView(
82         inflater: LayoutInflater,
83         container: ViewGroup?,
84         savedInstanceState: Bundle?,
85     ): View {
86         val rootView = super.onCreateView(inflater, container, savedInstanceState)
87 
88         contentParent = requireActivity().findViewById(android.R.id.content)
89         inflater.inflate(R.layout.widget_floating_action_button, contentParent)
90 
91         fab = contentParent.findViewById(R.id.extended_fab)
92         fab.isVisible = true
93 
94         recyclerView = rootView.findViewById(androidx.preference.R.id.recycler_view)
95         val bottomPadding =
96             resources.getDimensionPixelSize(R.dimen.recent_access_fab_bottom_padding)
97         recyclerView?.setPadding(0, 0, 0, bottomPadding)
98 
99         return rootView
100     }
101 
102     override fun onPause() {
103         // Prevents FAB from being permanently attached to the activity layout
104         contentParent.removeView(fab)
105         super.onPause()
106     }
107 
108     override fun onResume() {
109         super.onResume()
110         viewModel.loadRecentAccessApps()
111 
112         if (fab.parent == null) {
113             contentParent.addView(fab)
114         }
115         logger.logImpression(RecentAccessElement.MANAGE_PERMISSIONS_FAB)
116     }
117 
118     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
119         super.onViewCreated(view, savedInstanceState)
120 
121         viewModel.loadRecentAccessApps()
122         viewModel.recentAccessApps.observe(viewLifecycleOwner) { state ->
123             when (state) {
124                 is RecentAccessState.Loading -> {
125                     setLoading(true)
126                 }
127                 is RecentAccessState.Error -> {
128                     setError(true)
129                 }
130                 is RecentAccessState.WithData -> {
131                     setLoading(false)
132                     updateRecentApps(state.recentAccessEntries)
133                 }
134             }
135         }
136     }
137 
138     private fun updateRecentApps(recentAppsList: List<RecentAccessEntry>) {
139         mRecentAccessTodayPreferenceGroup.removeAll()
140         mRecentAccessYesterdayPreferenceGroup.removeAll()
141         mRecentAccessNoDataPreference.isVisible = false
142 
143         if (recentAppsList.isEmpty()) {
144             mRecentAccessYesterdayPreferenceGroup.isVisible = false
145             mRecentAccessTodayPreferenceGroup.isVisible = false
146             mRecentAccessNoDataPreference.isVisible = true
147             mRecentAccessNoDataPreference.isSelectable = false
148             fab.isVisible = false
149         } else {
150             // if the first entry is yesterday, we don't need the 'Today' section
151             mRecentAccessTodayPreferenceGroup.isVisible = recentAppsList[0].isToday
152 
153             // if the last entry is today, we don't need the 'Yesterday' section
154             mRecentAccessYesterdayPreferenceGroup.isVisible = !recentAppsList.last().isToday
155 
156             fab.setOnClickListener {
157                 logger.logInteraction(RecentAccessElement.MANAGE_PERMISSIONS_FAB)
158                 findNavController()
159                     .navigate(R.id.action_recentAccessFragment_to_connectedAppsFragment)
160             }
161 
162             recentAppsList.forEachIndexed { index, recentApp ->
163                 val isLastUsage =
164                     (index == recentAppsList.size - 1) ||
165                         (recentApp.isToday &&
166                             index < recentAppsList.size - 1 &&
167                             !recentAppsList[index + 1].isToday)
168                 val newPreference =
169                     RecentAccessPreference(requireContext(), recentApp, timeSource, true).also {
170                         it.setOnPreferenceClickListener {
171                             if (recentApp.isInactive) {
172                                 Toast.makeText(
173                                         requireContext(),
174                                         getString(R.string.recent_access_inactive_app),
175                                         Toast.LENGTH_LONG,
176                                     )
177                                     .show()
178                             } else {
179                                 if (
180                                     findNavController().currentDestination?.id ==
181                                         R.id.recentAccessFragment
182                                 ) {
183                                     navigateToAppInfoScreen(recentApp)
184                                 }
185                             }
186                             true
187                         }
188                     }
189 
190                 if (recentApp.isToday) {
191                     mRecentAccessTodayPreferenceGroup.addPreference(newPreference)
192                     if (!isLastUsage) {
193                         mRecentAccessTodayPreferenceGroup.addPreference(
194                             DividerPreference(requireContext())
195                         )
196                     }
197                 } else {
198                     mRecentAccessYesterdayPreferenceGroup.addPreference(newPreference)
199                     if (!isLastUsage) {
200                         mRecentAccessYesterdayPreferenceGroup.addPreference(
201                             DividerPreference(requireContext())
202                         )
203                     }
204                 }
205             }
206         }
207     }
208 
209     private fun navigateToAppInfoScreen(recentApp: RecentAccessEntry) {
210         val appPermissionsType = recentApp.appPermissionsType
211         val navigationId =
212             when (appPermissionsType) {
213                 AppPermissionsType.FITNESS_PERMISSIONS_ONLY ->
214                     R.id.action_recentAccessFragment_to_fitnessAppFragment
215                 AppPermissionsType.MEDICAL_PERMISSIONS_ONLY ->
216                     R.id.action_recentAccessFragment_to_medicalAppFragment
217                 AppPermissionsType.COMBINED_PERMISSIONS ->
218                     R.id.action_recentAccessFragment_to_combinedPermissionsFragment
219             }
220         findNavController()
221             .navigate(
222                 navigationId,
223                 bundleOf(
224                     Intent.EXTRA_PACKAGE_NAME to recentApp.metadata.packageName,
225                     Constants.EXTRA_APP_NAME to recentApp.metadata.appName,
226                 ),
227             )
228     }
229 }
230