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