| /** |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
| * in compliance with the License. You may obtain a copy of the License at |
| * |
| * ``` |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * ``` |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.android.healthconnect.controller.dataaccess |
| |
| import android.content.Intent.EXTRA_PACKAGE_NAME |
| import android.os.Bundle |
| import android.view.View |
| import androidx.core.os.bundleOf |
| import androidx.fragment.app.commitNow |
| import androidx.fragment.app.viewModels |
| import androidx.navigation.fragment.findNavController |
| import androidx.preference.Preference |
| import androidx.preference.PreferenceGroup |
| import com.android.healthconnect.controller.R |
| import com.android.healthconnect.controller.dataaccess.HealthDataAccessViewModel.DataAccessScreenState |
| import com.android.healthconnect.controller.deletion.DeletionConstants.DELETION_TYPE |
| import com.android.healthconnect.controller.deletion.DeletionConstants.FRAGMENT_TAG_DELETION |
| import com.android.healthconnect.controller.deletion.DeletionConstants.START_DELETION_EVENT |
| import com.android.healthconnect.controller.deletion.DeletionFragment |
| import com.android.healthconnect.controller.deletion.DeletionType |
| import com.android.healthconnect.controller.permissions.connectedapps.HealthAppPreference |
| import com.android.healthconnect.controller.permissions.data.HealthPermissionStrings.Companion.fromPermissionType |
| import com.android.healthconnect.controller.permissions.data.HealthPermissionType |
| import com.android.healthconnect.controller.permissiontypes.HealthPermissionTypesFragment.Companion.PERMISSION_TYPE_KEY |
| import com.android.healthconnect.controller.shared.HealthDataCategoryExtensions.fromHealthPermissionType |
| import com.android.healthconnect.controller.shared.HealthDataCategoryExtensions.icon |
| import com.android.healthconnect.controller.shared.app.AppMetadata |
| import com.android.healthconnect.controller.shared.inactiveapp.InactiveAppPreference |
| import com.android.healthconnect.controller.shared.preference.HealthPreference |
| import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment |
| import com.android.healthconnect.controller.utils.logging.DataAccessElement |
| import com.android.healthconnect.controller.utils.logging.HealthConnectLogger |
| import com.android.healthconnect.controller.utils.logging.PageName |
| import com.android.healthconnect.controller.utils.logging.ToolbarElement |
| import com.android.healthconnect.controller.utils.setTitle |
| import com.android.healthconnect.controller.utils.setupMenu |
| import com.android.settingslib.widget.AppHeaderPreference |
| import com.android.settingslib.widget.TopIntroPreference |
| import dagger.hilt.android.AndroidEntryPoint |
| import javax.inject.Inject |
| |
| /** Fragment displaying health data access information. */ |
| @AndroidEntryPoint(HealthPreferenceFragment::class) |
| class HealthDataAccessFragment : Hilt_HealthDataAccessFragment() { |
| |
| companion object { |
| private const val DATA_ACCESS_HEADER = "data_access_header" |
| private const val PERMISSION_TYPE_DESCRIPTION = "permission_type_description" |
| private const val CAN_READ_SECTION = "can_read" |
| private const val CAN_WRITE_SECTION = "can_write" |
| private const val INACTIVE_SECTION = "inactive" |
| private const val ALL_ENTRIES_BUTTON = "all_entries_button" |
| private const val DELETE_PERMISSION_TYPE_DATA_BUTTON = "delete_permission_type_data" |
| } |
| |
| init { |
| this.setPageName(PageName.DATA_ACCESS_PAGE) |
| } |
| |
| @Inject lateinit var logger: HealthConnectLogger |
| |
| private val viewModel: HealthDataAccessViewModel by viewModels() |
| |
| private lateinit var permissionType: HealthPermissionType |
| |
| private val mDataAccessHeader: AppHeaderPreference? by lazy { |
| preferenceScreen.findPreference(DATA_ACCESS_HEADER) |
| } |
| |
| private val mPermissionTypeDescription: TopIntroPreference? by lazy { |
| preferenceScreen.findPreference(PERMISSION_TYPE_DESCRIPTION) |
| } |
| |
| private val mCanReadSection: PreferenceGroup? by lazy { |
| preferenceScreen.findPreference(CAN_READ_SECTION) |
| } |
| |
| private val mCanWriteSection: PreferenceGroup? by lazy { |
| preferenceScreen.findPreference(CAN_WRITE_SECTION) |
| } |
| |
| private val mInactiveSection: PreferenceGroup? by lazy { |
| preferenceScreen.findPreference(INACTIVE_SECTION) |
| } |
| |
| private val mAllEntriesButton: HealthPreference? by lazy { |
| preferenceScreen.findPreference(ALL_ENTRIES_BUTTON) |
| } |
| |
| private val mDeletePermissionTypeData: HealthPreference? by lazy { |
| preferenceScreen.findPreference(DELETE_PERMISSION_TYPE_DATA_BUTTON) |
| } |
| |
| override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { |
| super.onCreatePreferences(savedInstanceState, rootKey) |
| setPreferencesFromResource(R.xml.health_data_access_screen, rootKey) |
| if (requireArguments().containsKey(PERMISSION_TYPE_KEY)) { |
| permissionType = |
| arguments?.getSerializable(PERMISSION_TYPE_KEY, HealthPermissionType::class.java) |
| ?: throw IllegalArgumentException("PERMISSION_TYPE_KEY can't be null!") |
| } |
| |
| mCanReadSection?.isVisible = false |
| mCanWriteSection?.isVisible = false |
| mInactiveSection?.isVisible = false |
| maybeShowPermissionTypeDescription() |
| mCanReadSection?.title = |
| getString( |
| R.string.can_read, getString(fromPermissionType(permissionType).lowercaseLabel)) |
| mCanWriteSection?.title = |
| getString( |
| R.string.can_write, getString(fromPermissionType(permissionType).lowercaseLabel)) |
| if (childFragmentManager.findFragmentByTag(FRAGMENT_TAG_DELETION) == null) { |
| childFragmentManager.commitNow { add(DeletionFragment(), FRAGMENT_TAG_DELETION) } |
| } |
| |
| mAllEntriesButton?.logName = DataAccessElement.SEE_ALL_ENTRIES_BUTTON |
| mAllEntriesButton?.setOnPreferenceClickListener { |
| findNavController() |
| .navigate( |
| R.id.action_healthDataAccess_to_dataEntries, |
| bundleOf(PERMISSION_TYPE_KEY to permissionType)) |
| true |
| } |
| mDeletePermissionTypeData?.logName = DataAccessElement.DELETE_THIS_DATA_BUTTON |
| mDeletePermissionTypeData?.setOnPreferenceClickListener { |
| val deletionType = |
| DeletionType.DeletionTypeHealthPermissionTypeData( |
| healthPermissionType = permissionType) |
| childFragmentManager.setFragmentResult( |
| START_DELETION_EVENT, bundleOf(DELETION_TYPE to deletionType)) |
| true |
| } |
| } |
| |
| private fun maybeShowPermissionTypeDescription() { |
| mPermissionTypeDescription?.isVisible = false |
| if (permissionType == HealthPermissionType.EXERCISE) { |
| mPermissionTypeDescription?.isVisible = true |
| mPermissionTypeDescription?.setTitle(R.string.data_access_exercise_description) |
| } |
| if (permissionType == HealthPermissionType.SLEEP) { |
| mPermissionTypeDescription?.isVisible = true |
| mPermissionTypeDescription?.setTitle(R.string.data_access_sleep_description) |
| } |
| } |
| |
| override fun onResume() { |
| super.onResume() |
| setTitle(fromPermissionType(permissionType).uppercaseLabel) |
| viewModel.loadAppMetaDataMap(permissionType) |
| } |
| |
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| super.onViewCreated(view, savedInstanceState) |
| |
| mDataAccessHeader?.icon = fromHealthPermissionType(permissionType).icon(requireContext()) |
| mDataAccessHeader?.title = getString(fromPermissionType(permissionType).uppercaseLabel) |
| viewModel.loadAppMetaDataMap(permissionType) |
| viewModel.appMetadataMap.observe(viewLifecycleOwner) { state -> |
| when (state) { |
| is DataAccessScreenState.Loading -> { |
| setLoading(isLoading = true) |
| } |
| is DataAccessScreenState.Error -> { |
| setError(hasError = true) |
| } |
| is DataAccessScreenState.WithData -> { |
| setLoading(isLoading = false, animate = false) |
| updateDataAccess(state.appMetadata) |
| } |
| } |
| } |
| |
| setupMenu(R.menu.set_data_units_with_send_feedback_and_help, viewLifecycleOwner, logger) { |
| menuItem -> |
| when (menuItem.itemId) { |
| R.id.menu_open_units -> { |
| logger.logImpression(ToolbarElement.TOOLBAR_UNITS_BUTTON) |
| findNavController() |
| .navigate(R.id.action_healthDataAccessFragment_to_unitsFragment) |
| true |
| } |
| else -> false |
| } |
| } |
| } |
| |
| private fun updateDataAccess(appMetadataMap: Map<DataAccessAppState, List<AppMetadata>>) { |
| mCanReadSection?.removeAll() |
| mCanWriteSection?.removeAll() |
| mInactiveSection?.removeAll() |
| |
| if (appMetadataMap.containsKey(DataAccessAppState.Read)) { |
| if (appMetadataMap[DataAccessAppState.Read]!!.isEmpty()) { |
| mCanReadSection?.isVisible = false |
| } else { |
| mCanReadSection?.isVisible = true |
| appMetadataMap[DataAccessAppState.Read]!!.forEach { _appMetadata -> |
| mCanReadSection?.addPreference(createAppPreference(_appMetadata)) |
| } |
| } |
| } |
| if (appMetadataMap.containsKey(DataAccessAppState.Write)) { |
| if (appMetadataMap[DataAccessAppState.Write]!!.isEmpty()) { |
| mCanWriteSection?.isVisible = false |
| } else { |
| mCanWriteSection?.isVisible = true |
| appMetadataMap[DataAccessAppState.Write]!!.forEach { _appMetadata -> |
| mCanWriteSection?.addPreference(createAppPreference(_appMetadata)) |
| } |
| } |
| } |
| if (appMetadataMap.containsKey(DataAccessAppState.Inactive)) { |
| if (appMetadataMap[DataAccessAppState.Inactive]!!.isEmpty()) { |
| mInactiveSection?.isVisible = false |
| } else { |
| mInactiveSection?.isVisible = true |
| mInactiveSection?.addPreference( |
| Preference(requireContext()).also { |
| it.summary = |
| getString( |
| R.string.inactive_apps_message, |
| getString(fromPermissionType(permissionType).lowercaseLabel)) |
| }) |
| appMetadataMap[DataAccessAppState.Inactive]?.forEach { _appMetadata -> |
| mInactiveSection?.addPreference( |
| InactiveAppPreference(requireContext()).also { |
| it.title = _appMetadata.appName |
| it.icon = _appMetadata.icon |
| it.logName = DataAccessElement.DATA_ACCESS_INACTIVE_APP_BUTTON |
| it.setOnDeleteButtonClickListener { |
| val deletionType = |
| DeletionType.DeletionTypeAppData( |
| _appMetadata.packageName, _appMetadata.appName) |
| childFragmentManager.setFragmentResult( |
| START_DELETION_EVENT, bundleOf(DELETION_TYPE to deletionType)) |
| } |
| }) |
| } |
| } |
| } |
| } |
| |
| private fun createAppPreference(appMetadata: AppMetadata): HealthAppPreference { |
| return HealthAppPreference(requireContext(), appMetadata).also { |
| it.logName = DataAccessElement.DATA_ACCESS_APP_BUTTON |
| it.setOnPreferenceClickListener { |
| // TODO (b/270859815) might need to navigate to appAccess instead |
| findNavController() |
| .navigate( |
| R.id.action_healthDataAccessFragment_to_appAccess, |
| bundleOf(EXTRA_PACKAGE_NAME to appMetadata.packageName)) |
| true |
| } |
| } |
| } |
| } |