1 /*
<lambda>null2  * Copyright (C) 2023 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  */
18 
19 /**
20  * Copyright (C) 2022 The Android Open Source Project
21  *
22  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23  * in compliance with the License. You may obtain a copy of the License at
24  *
25  * ```
26  *      http://www.apache.org/licenses/LICENSE-2.0
27  * ```
28  *
29  * Unless required by applicable law or agreed to in writing, software distributed under the License
30  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
31  * or implied. See the License for the specific language governing permissions and limitations under
32  * the License.
33  */
34 package com.android.healthconnect.controller.permissions.connectedapps
35 
36 import android.content.Intent.EXTRA_PACKAGE_NAME
37 import android.os.Bundle
38 import android.view.View
39 import androidx.annotation.StringRes
40 import androidx.core.os.bundleOf
41 import androidx.fragment.app.viewModels
42 import androidx.navigation.fragment.findNavController
43 import androidx.preference.Preference
44 import androidx.preference.PreferenceGroup
45 import com.android.healthconnect.controller.R
46 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.maybeShowWhatsNewDialog
47 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.showDataRestoreInProgressDialog
48 import com.android.healthconnect.controller.migration.MigrationActivity.Companion.showMigrationInProgressDialog
49 import com.android.healthconnect.controller.migration.MigrationViewModel
50 import com.android.healthconnect.controller.migration.api.MigrationRestoreState
51 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiState
52 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.MigrationUiState
53 import com.android.healthconnect.controller.shared.Constants.EXTRA_APP_NAME
54 import com.android.healthconnect.controller.shared.app.ConnectedAppMetadata
55 import com.android.healthconnect.controller.shared.app.ConnectedAppStatus.ALLOWED
56 import com.android.healthconnect.controller.shared.app.ConnectedAppStatus.DENIED
57 import com.android.healthconnect.controller.shared.preference.HealthPreferenceFragment
58 import com.android.healthconnect.controller.utils.dismissLoadingDialog
59 import com.android.healthconnect.controller.utils.logging.AppPermissionsElement
60 import com.android.healthconnect.controller.utils.logging.PageName
61 import com.android.healthconnect.controller.utils.showLoadingDialog
62 import com.android.settingslib.widget.AppPreference
63 import dagger.hilt.android.AndroidEntryPoint
64 
65 /**
66  * Fragment to show allowed and denied apps for health permissions. It is used as an entry point
67  * from PermissionController.
68  */
69 @AndroidEntryPoint(HealthPreferenceFragment::class)
70 class SettingsManagePermissionFragment : Hilt_SettingsManagePermissionFragment() {
71 
72     companion object {
73         const val ALLOWED_APPS_GROUP = "allowed_apps"
74         const val DENIED_APPS_GROUP = "denied_apps"
75     }
76 
77     init {
78         this.setPageName(PageName.SETTINGS_MANAGE_PERMISSIONS_PAGE)
79     }
80 
81     private val allowedAppsGroup: PreferenceGroup? by lazy {
82         preferenceScreen.findPreference(ALLOWED_APPS_GROUP)
83     }
84 
85     private val deniedAppsGroup: PreferenceGroup? by lazy {
86         preferenceScreen.findPreference(DENIED_APPS_GROUP)
87     }
88 
89     private val viewModel: ConnectedAppsViewModel by viewModels()
90     private val migrationViewModel: MigrationViewModel by viewModels()
91 
92     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
93         super.onCreatePreferences(savedInstanceState, rootKey)
94         setPreferencesFromResource(R.xml.settings_manage_permission_screen, rootKey)
95     }
96 
97     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
98         super.onViewCreated(view, savedInstanceState)
99         viewModel.connectedApps.observe(viewLifecycleOwner) { connectedApps ->
100             val connectedAppsGroup = connectedApps.groupBy { it.status }
101             updateAllowedApps(connectedAppsGroup[ALLOWED].orEmpty())
102             updateDeniedApps(connectedAppsGroup[DENIED].orEmpty())
103         }
104         viewModel.disconnectAllState.observe(viewLifecycleOwner) { state ->
105             when (state) {
106                 is ConnectedAppsViewModel.DisconnectAllState.Loading -> {
107                     showLoadingDialog()
108                 }
109                 else -> {
110                     dismissLoadingDialog()
111                 }
112             }
113         }
114         migrationViewModel.migrationState.observe(viewLifecycleOwner) { migrationState ->
115             when (migrationState) {
116                 is MigrationViewModel.MigrationFragmentState.WithData -> {
117                     maybeShowMigrationDialog(migrationState.migrationRestoreState)
118                 }
119                 else -> {
120                     // do nothing
121                 }
122             }
123         }
124     }
125 
126     private fun maybeShowMigrationDialog(migrationRestoreState: MigrationRestoreState) {
127         val (migrationUiState, dataRestoreUiState, dataErrorState) = migrationRestoreState
128 
129         if (dataRestoreUiState == DataRestoreUiState.IN_PROGRESS) {
130             showDataRestoreInProgressDialog(requireContext()) { _, _ -> requireActivity().finish() }
131         } else if (migrationUiState == MigrationUiState.IN_PROGRESS) {
132             showMigrationInProgressDialog(
133                 requireContext(),
134                 getString(R.string.migration_in_progress_permissions_dialog_content_apps),
135             ) { _, _ ->
136                 requireActivity().finish()
137             }
138         } else if (migrationUiState == MigrationUiState.COMPLETE) {
139             maybeShowWhatsNewDialog(requireContext())
140         }
141     }
142 
143     override fun onResume() {
144         super.onResume()
145         viewModel.loadConnectedApps()
146     }
147 
148     private fun updateAllowedApps(appsList: List<ConnectedAppMetadata>) {
149         allowedAppsGroup?.removeAll()
150         if (appsList.isEmpty()) {
151             allowedAppsGroup?.addPreference(getNoAppsPreference(R.string.no_apps_allowed))
152         } else {
153             appsList.forEach { app -> allowedAppsGroup?.addPreference(getAppPreference(app)) }
154         }
155     }
156 
157     private fun updateDeniedApps(appsList: List<ConnectedAppMetadata>) {
158         deniedAppsGroup?.removeAll()
159 
160         if (appsList.isEmpty()) {
161             deniedAppsGroup?.addPreference(getNoAppsPreference(R.string.no_apps_denied))
162         } else {
163             appsList.forEach { app -> deniedAppsGroup?.addPreference(getAppPreference(app)) }
164         }
165     }
166 
167     private fun getNoAppsPreference(@StringRes res: Int): Preference {
168         return Preference(requireContext()).also {
169             it.setTitle(res)
170             it.isSelectable = false
171         }
172     }
173 
174     private fun getAppPreference(app: ConnectedAppMetadata): AppPreference {
175         return HealthAppPreference(requireContext(), app.appMetadata).also {
176             if (app.status == ALLOWED) {
177                 it.logName = AppPermissionsElement.CONNECTED_APP_BUTTON
178             } else if (app.status == DENIED) {
179                 it.logName = AppPermissionsElement.NOT_CONNECTED_APP_BUTTON
180             }
181             if (app.healthUsageLastAccess != null) {
182                 it.setSummary(R.string.app_perms_content_provider_24h)
183             } else {
184                 it.summary = null
185             }
186             it.setOnPreferenceClickListener {
187                 findNavController()
188                     .navigate(
189                         R.id.action_settingsManagePermission_to_settingsManageAppPermissions,
190                         bundleOf(
191                             EXTRA_PACKAGE_NAME to app.appMetadata.packageName,
192                             EXTRA_APP_NAME to app.appMetadata.appName))
193                 true
194             }
195         }
196     }
197 }
198