• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.healthconnect.controller.migration
17 
18 import android.app.Activity
19 import android.content.Context
20 import android.content.DialogInterface
21 import android.content.Intent
22 import android.os.Bundle
23 import android.util.Log
24 import android.view.WindowManager
25 import androidx.fragment.app.FragmentActivity
26 import androidx.navigation.findNavController
27 import com.android.healthconnect.controller.R
28 import com.android.healthconnect.controller.migration.api.MigrationRestoreState
29 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiState
30 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.MigrationUiState
31 import com.android.healthconnect.controller.shared.Constants.APP_UPDATE_NEEDED_SEEN
32 import com.android.healthconnect.controller.shared.Constants.INTEGRATION_PAUSED_SEEN_KEY
33 import com.android.healthconnect.controller.shared.Constants.MODULE_UPDATE_NEEDED_SEEN
34 import com.android.healthconnect.controller.shared.Constants.USER_ACTIVITY_TRACKER
35 import com.android.healthconnect.controller.shared.Constants.WHATS_NEW_DIALOG_SEEN
36 import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder
37 import com.android.healthconnect.controller.utils.DeviceInfoUtils
38 import com.android.healthconnect.controller.utils.logging.DataRestoreElement
39 import com.android.healthconnect.controller.utils.logging.MigrationElement
40 import com.android.settingslib.collapsingtoolbar.EdgeToEdgeUtils
41 import dagger.hilt.android.AndroidEntryPoint
42 import javax.inject.Inject
43 
44 /** Activity in charge of coordinating migration navigation, fragments and dialogs. */
45 @AndroidEntryPoint(FragmentActivity::class)
46 class MigrationActivity : Hilt_MigrationActivity() {
47 
48     companion object {
49         private const val TAG = "MigrationActivity"
50         const val MIGRATION_ACTIVITY_INTENT = "android.health.connect.action.MIGRATION"
51 
52         fun maybeRedirectToMigrationActivity(
53             activity: Activity,
54             migrationRestoreState: MigrationRestoreState,
55         ): Boolean {
56 
57             val sharedPreference =
58                 activity.getSharedPreferences(USER_ACTIVITY_TRACKER, Context.MODE_PRIVATE)
59 
60             if (
61                 migrationRestoreState.migrationUiState == MigrationUiState.MODULE_UPGRADE_REQUIRED
62             ) {
63                 val moduleUpdateSeen = sharedPreference.getBoolean(MODULE_UPDATE_NEEDED_SEEN, false)
64 
65                 if (!moduleUpdateSeen) {
66                     activity.startActivity(createMigrationActivityIntent(activity))
67                     activity.finish()
68                     return true
69                 }
70             } else if (
71                 migrationRestoreState.migrationUiState == MigrationUiState.APP_UPGRADE_REQUIRED
72             ) {
73                 val appUpdateSeen = sharedPreference.getBoolean(APP_UPDATE_NEEDED_SEEN, false)
74 
75                 if (!appUpdateSeen) {
76                     activity.startActivity(createMigrationActivityIntent(activity))
77                     activity.finish()
78                     return true
79                 }
80             } else if (
81                 migrationRestoreState.migrationUiState == MigrationUiState.ALLOWED_PAUSED ||
82                     migrationRestoreState.migrationUiState == MigrationUiState.ALLOWED_NOT_STARTED
83             ) {
84                 val allowedPausedSeen =
85                     sharedPreference.getBoolean(INTEGRATION_PAUSED_SEEN_KEY, false)
86 
87                 if (!allowedPausedSeen) {
88                     activity.startActivity(createMigrationActivityIntent(activity))
89                     activity.finish()
90                     return true
91                 }
92             } else if (
93                 migrationRestoreState.migrationUiState == MigrationUiState.IN_PROGRESS ||
94                     migrationRestoreState.dataRestoreState == DataRestoreUiState.IN_PROGRESS
95             ) {
96                 activity.startActivity(createMigrationActivityIntent(activity))
97                 activity.finish()
98                 return true
99             }
100 
101             return false
102         }
103 
104         fun maybeShowMigrationDialog(
105             migrationRestoreState: MigrationRestoreState,
106             activity: FragmentActivity,
107             appName: String,
108         ) {
109             val (migrationUiState, dataRestoreUiState, dataErrorState) = migrationRestoreState
110 
111             when {
112                 dataRestoreUiState == DataRestoreUiState.IN_PROGRESS -> {
113                     showDataRestoreInProgressDialog(activity) { _, _ -> activity.finish() }
114                 }
115                 migrationUiState == MigrationUiState.IN_PROGRESS -> {
116                     val message =
117                         activity.getString(
118                             R.string.migration_in_progress_permissions_dialog_content,
119                             appName,
120                         )
121                     showMigrationInProgressDialog(activity, message) { _, _ -> activity.finish() }
122                 }
123                 migrationUiState in
124                     listOf(
125                         MigrationUiState.ALLOWED_PAUSED,
126                         MigrationUiState.ALLOWED_NOT_STARTED,
127                         MigrationUiState.APP_UPGRADE_REQUIRED,
128                         MigrationUiState.MODULE_UPGRADE_REQUIRED,
129                     ) -> {
130                     val message =
131                         activity.getString(
132                             R.string.migration_pending_permissions_dialog_content,
133                             appName,
134                         )
135                     showMigrationPendingDialog(
136                         activity,
137                         message,
138                         positiveButtonAction = null,
139                         negativeButtonAction = { _, _ ->
140                             activity.startActivity(Intent(MIGRATION_ACTIVITY_INTENT))
141                             activity.finish()
142                         },
143                     )
144                 }
145                 migrationUiState == MigrationUiState.COMPLETE -> {
146                     maybeShowWhatsNewDialog(activity)
147                 }
148                 else -> {
149                     // show nothing
150                 }
151             }
152         }
153 
154         fun showMigrationPendingDialog(
155             context: Context,
156             message: String,
157             positiveButtonAction: DialogInterface.OnClickListener? = null,
158             negativeButtonAction: DialogInterface.OnClickListener? = null,
159         ) {
160             AlertDialogBuilder(context, MigrationElement.MIGRATION_PENDING_DIALOG_CONTAINER)
161                 .setTitle(R.string.migration_pending_permissions_dialog_title)
162                 .setMessage(message)
163                 .setCancelable(false)
164                 .setNeutralButton(
165                     R.string.migration_pending_permissions_dialog_button_start_integration,
166                     MigrationElement.MIGRATION_PENDING_DIALOG_CANCEL_BUTTON,
167                     negativeButtonAction,
168                 )
169                 .setPositiveButton(
170                     R.string.migration_pending_permissions_dialog_button_continue,
171                     MigrationElement.MIGRATION_PENDING_DIALOG_CONTINUE_BUTTON,
172                     positiveButtonAction,
173                 )
174                 .create()
175                 .show()
176         }
177 
178         fun showMigrationInProgressDialog(
179             context: Context,
180             message: String,
181             negativeButtonAction: DialogInterface.OnClickListener? = null,
182         ) {
183             AlertDialogBuilder(context, MigrationElement.MIGRATION_IN_PROGRESS_DIALOG_CONTAINER)
184                 .setTitle(R.string.migration_in_progress_permissions_dialog_title)
185                 .setMessage(message)
186                 .setCancelable(false)
187                 .setNegativeButton(
188                     R.string.migration_in_progress_permissions_dialog_button_got_it,
189                     MigrationElement.MIGRATION_IN_PROGRESS_DIALOG_BUTTON,
190                     negativeButtonAction,
191                 )
192                 .create()
193                 .show()
194         }
195 
196         fun showDataRestoreInProgressDialog(
197             context: Context,
198             negativeButtonAction: DialogInterface.OnClickListener? = null,
199         ) {
200             AlertDialogBuilder(context, DataRestoreElement.RESTORE_IN_PROGRESS_DIALOG_CONTAINER)
201                 .setTitle(R.string.data_restore_in_progress_dialog_title)
202                 .setMessage(R.string.data_restore_in_progress_content)
203                 .setCancelable(false)
204                 .setNegativeButton(
205                     R.string.data_restore_in_progress_dialog_button,
206                     DataRestoreElement.RESTORE_IN_PROGRESS_DIALOG_BUTTON,
207                     negativeButtonAction,
208                 )
209                 .create()
210                 .show()
211         }
212 
213         fun maybeShowWhatsNewDialog(
214             context: Context,
215             negativeButtonAction: DialogInterface.OnClickListener? = null,
216         ) {
217             val sharedPreference =
218                 context.getSharedPreferences(USER_ACTIVITY_TRACKER, Context.MODE_PRIVATE)
219             val dialogSeen = sharedPreference.getBoolean(WHATS_NEW_DIALOG_SEEN, false)
220 
221             if (!dialogSeen) {
222                 AlertDialogBuilder(context, MigrationElement.MIGRATION_DONE_DIALOG_CONTAINER)
223                     .setTitle(R.string.migration_whats_new_dialog_title)
224                     .setMessage(R.string.migration_whats_new_dialog_content)
225                     .setCancelable(false)
226                     .setNegativeButton(
227                         R.string.migration_whats_new_dialog_button,
228                         MigrationElement.MIGRATION_DONE_DIALOG_BUTTON,
229                     ) { unusedDialogInterface, unusedInt ->
230                         sharedPreference.edit().apply {
231                             putBoolean(WHATS_NEW_DIALOG_SEEN, true)
232                             apply()
233                         }
234                         negativeButtonAction?.onClick(unusedDialogInterface, unusedInt)
235                     }
236                     .create()
237                     .show()
238             }
239         }
240 
241         private fun createMigrationActivityIntent(context: Context): Intent {
242             return Intent(context, MigrationActivity::class.java).apply {
243                 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
244                 addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
245             }
246         }
247     }
248 
249     @Inject lateinit var deviceInfoUtils: DeviceInfoUtils
250 
251     override fun onCreate(savedInstanceState: Bundle?) {
252         super.onCreate(savedInstanceState)
253         // This flag ensures a non system app cannot show an overlay on Health Connect. b/313425281
254         window.addSystemFlags(
255             WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
256         )
257 
258         // Handles unsupported devices and user profiles.
259         if (!deviceInfoUtils.isHealthConnectAvailable(this)) {
260             Log.e(TAG, "Health connect is not available for this user or hardware, finishing!")
261             finish()
262             return
263         }
264 
265         setContentView(R.layout.activity_migration)
266         EdgeToEdgeUtils.enable(this)
267     }
268 
269     override fun onBackPressed() {
270         val navController = findNavController(R.id.nav_host_fragment)
271         if (!navController.popBackStack()) {
272             finish()
273         }
274     }
275 
276     override fun onNavigateUp(): Boolean {
277         val navController = findNavController(R.id.nav_host_fragment)
278         if (!navController.popBackStack()) {
279             finish()
280         }
281         return true
282     }
283 }
284