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