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