1 /*
2  * 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 package com.android.server.healthconnect.migration;
18 
19 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_ALLOWED_ERROR;
20 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_ALLOWED_MIGRATOR_DISABLED;
21 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_ALLOWED_NOT_STARTED;
22 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_ALLOWED_PAUSED;
23 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_APP_UPGRADE_REQUIRED;
24 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_COMPLETE;
25 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_COMPLETE_IDLE;
26 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_IDLE;
27 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_IN_PROGRESS;
28 import static android.health.connect.migration.HealthConnectMigrationUiState.MIGRATION_UI_STATE_MODULE_UPGRADE_REQUIRED;
29 
30 import android.annotation.NonNull;
31 import android.content.Context;
32 import android.health.connect.HealthConnectDataState;
33 import android.health.connect.migration.HealthConnectMigrationUiState;
34 import android.os.UserHandle;
35 
36 import com.android.server.healthconnect.migration.notification.MigrationNotificationSender;
37 
38 /**
39  * Sends the appropriate notification based on the migration state, and returns the UI state to the
40  * caller.
41  *
42  * @hide
43  */
44 public class MigrationUiStateManager {
45 
46     private final Context mContext;
47     private volatile UserHandle mUserHandle;
48     private final MigrationNotificationSender mMigrationNotificationSender;
49     private final MigrationStateManager mMigrationStateManager;
50     private static final String TAG = "MigrationUiStateManager";
51 
MigrationUiStateManager( @onNull Context context, @NonNull UserHandle userHandle, @NonNull MigrationStateManager migrationStateManager, @NonNull MigrationNotificationSender migrationNotificationSender)52     public MigrationUiStateManager(
53             @NonNull Context context,
54             @NonNull UserHandle userHandle,
55             @NonNull MigrationStateManager migrationStateManager,
56             @NonNull MigrationNotificationSender migrationNotificationSender) {
57         this.mContext = context;
58         this.mUserHandle = userHandle;
59         this.mMigrationNotificationSender = migrationNotificationSender;
60         this.mMigrationStateManager = migrationStateManager;
61     }
62 
63     /** Assigns a new user handle to this object. */
setUserHandle(@onNull UserHandle userHandle)64     public void setUserHandle(@NonNull UserHandle userHandle) {
65         this.mUserHandle = userHandle;
66     }
67 
68     /** Attaches this MigrationUiStateManager to the provided {@link MigrationStateManager}. */
attachTo(@onNull MigrationStateManager migrationStateManager)69     public void attachTo(@NonNull MigrationStateManager migrationStateManager) {
70         migrationStateManager.addStateChangedListener(this::onMigrationStateChanged);
71     }
72 
73     /** Returns the current Migration UI state. */
74     @HealthConnectMigrationUiState.Type
getHealthConnectMigrationUiState()75     public int getHealthConnectMigrationUiState() {
76         // get current migration state
77         @HealthConnectDataState.DataMigrationState
78         int migrationState = mMigrationStateManager.getMigrationState();
79 
80         switch (migrationState) {
81             case HealthConnectDataState.MIGRATION_STATE_IDLE:
82                 return MIGRATION_UI_STATE_IDLE;
83 
84             case HealthConnectDataState.MIGRATION_STATE_APP_UPGRADE_REQUIRED:
85                 return MIGRATION_UI_STATE_APP_UPGRADE_REQUIRED;
86 
87             case HealthConnectDataState.MIGRATION_STATE_MODULE_UPGRADE_REQUIRED:
88                 return MIGRATION_UI_STATE_MODULE_UPGRADE_REQUIRED;
89 
90             case HealthConnectDataState.MIGRATION_STATE_ALLOWED:
91                 if (isUiStateAllowedPaused()) {
92                     return MIGRATION_UI_STATE_ALLOWED_NOT_STARTED;
93                 } else if (isMigratorDisabled()) {
94                     return MIGRATION_UI_STATE_ALLOWED_ERROR;
95                 } else if (isUiStateInProgressPaused()) {
96                     return MIGRATION_UI_STATE_ALLOWED_PAUSED;
97                 } else {
98                     return MIGRATION_UI_STATE_ALLOWED_MIGRATOR_DISABLED;
99                 }
100 
101             case HealthConnectDataState.MIGRATION_STATE_IN_PROGRESS:
102                 return MIGRATION_UI_STATE_IN_PROGRESS;
103 
104             case HealthConnectDataState.MIGRATION_STATE_COMPLETE:
105                 if (isUiStateCompleteFromIdle()) {
106                     return MIGRATION_UI_STATE_COMPLETE_IDLE;
107                 } else {
108                     return MIGRATION_UI_STATE_COMPLETE;
109                 }
110 
111             default:
112                 throw new IllegalArgumentException(
113                         "Cannot compute migration UI state. Unknown migration state: "
114                                 + migrationState);
115         }
116     }
117 
onMigrationStateChanged(@ealthConnectDataState.DataMigrationState int newState)118     private void onMigrationStateChanged(@HealthConnectDataState.DataMigrationState int newState) {
119 
120         @HealthConnectMigrationUiState.Type
121         int migrationUiState = getHealthConnectMigrationUiState();
122 
123         switch (migrationUiState) {
124             case MIGRATION_UI_STATE_ALLOWED_PAUSED:
125                 mMigrationNotificationSender.sendNotification(
126                         MigrationNotificationSender.NOTIFICATION_TYPE_MIGRATION_PAUSED,
127                         mUserHandle);
128                 break;
129 
130             case MIGRATION_UI_STATE_MODULE_UPGRADE_REQUIRED:
131                 mMigrationNotificationSender.sendNotification(
132                         MigrationNotificationSender
133                                 .NOTIFICATION_TYPE_MIGRATION_MODULE_UPDATE_NEEDED,
134                         mUserHandle);
135                 break;
136             default:
137                 mMigrationNotificationSender.clearNotifications(mUserHandle);
138                 break;
139         }
140     }
141 
isUiStateCompleteFromIdle()142     private boolean isUiStateCompleteFromIdle() {
143         return mMigrationStateManager.hasIdleStateTimedOut();
144     }
145 
isUiStateAllowedPaused()146     private boolean isUiStateAllowedPaused() {
147         // Migrator not responding to broadcast before migration started
148         int migrationStartsCount = mMigrationStateManager.getMigrationStartsCount();
149         boolean isApkInstalled =
150                 mMigrationStateManager.existsMigrationAwarePackage(mContext)
151                         && mMigrationStateManager.existsMigratorPackage(mContext);
152         boolean doesMigratorHandleInfoIntent =
153                 mMigrationStateManager.doesMigratorHandleInfoIntent(mContext);
154 
155         return migrationStartsCount == 0 && isApkInstalled && doesMigratorHandleInfoIntent;
156     }
157 
isMigratorDisabled()158     private boolean isMigratorDisabled() {
159         int migrationStartsCount = mMigrationStateManager.getMigrationStartsCount();
160         boolean doesMigratorHandleInfoIntent =
161                 mMigrationStateManager.doesMigratorHandleInfoIntent(mContext);
162 
163         return migrationStartsCount > 0 && !doesMigratorHandleInfoIntent;
164     }
165 
isUiStateInProgressPaused()166     private boolean isUiStateInProgressPaused() {
167         int migrationStartsCount = mMigrationStateManager.getMigrationStartsCount();
168         boolean doesMigratorHandleInfoIntent =
169                 mMigrationStateManager.doesMigratorHandleInfoIntent(mContext);
170         boolean hasInProgressStateTimedOut = mMigrationStateManager.hasInProgressStateTimedOut();
171 
172         return migrationStartsCount > 0
173                 && doesMigratorHandleInfoIntent
174                 && hasInProgressStateTimedOut;
175     }
176 }
177