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