• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.server.healthconnect.backuprestore;
17 
18 import static android.health.connect.datatypes.RecordTypeIdentifier.RECORD_TYPE_UNKNOWN;
19 
20 import static com.android.server.healthconnect.backuprestore.RecordProtoConverter.PROTO_VERSION;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.database.sqlite.SQLiteException;
25 import android.health.connect.backuprestore.BackupMetadata;
26 import android.health.connect.backuprestore.GetChangesForBackupResponse;
27 import android.health.connect.backuprestore.GetLatestMetadataForBackupResponse;
28 import android.health.connect.internal.datatypes.utils.HealthConnectMappings;
29 import android.util.Slog;
30 
31 import com.android.server.healthconnect.fitness.FitnessRecordReadHelper;
32 import com.android.server.healthconnect.logging.BackupRestoreLogger;
33 import com.android.server.healthconnect.storage.TransactionManager;
34 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper;
35 import com.android.server.healthconnect.storage.datatypehelpers.BackupChangeTokenHelper;
36 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsHelper;
37 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsRequestHelper;
38 import com.android.server.healthconnect.storage.datatypehelpers.DeviceInfoHelper;
39 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper;
40 import com.android.server.healthconnect.storage.datatypehelpers.PreferenceHelper;
41 import com.android.server.healthconnect.storage.utils.InternalHealthConnectMappings;
42 
43 import java.time.Clock;
44 
45 /**
46  * Manages Cloud Backup operations.
47  *
48  * @hide
49  */
50 public final class CloudBackupManager {
51 
52     private static final String TAG = "CloudBackupManager";
53 
54     private final TransactionManager mTransactionManager;
55     private final CloudBackupDatabaseHelper mDatabaseHelper;
56     private final HealthDataCategoryPriorityHelper mPriorityHelper;
57     private final PreferenceHelper mPreferenceHelper;
58     private final AppInfoHelper mAppInfoHelper;
59     private final Clock mClock;
60     private final BackupRestoreLogger mBackupRestoreLogger;
61 
CloudBackupManager( TransactionManager transactionManager, FitnessRecordReadHelper fitnessRecordReadHelper, AppInfoHelper appInfoHelper, DeviceInfoHelper deviceInfoHelper, HealthConnectMappings healthConnectMappings, InternalHealthConnectMappings internalHealthConnectMappings, ChangeLogsHelper changeLogsHelper, ChangeLogsRequestHelper changeLogsRequestHelper, HealthDataCategoryPriorityHelper priorityHelper, PreferenceHelper preferenceHelper, Clock clock, BackupRestoreLogger backupRestoreLogger)62     public CloudBackupManager(
63             TransactionManager transactionManager,
64             FitnessRecordReadHelper fitnessRecordReadHelper,
65             AppInfoHelper appInfoHelper,
66             DeviceInfoHelper deviceInfoHelper,
67             HealthConnectMappings healthConnectMappings,
68             InternalHealthConnectMappings internalHealthConnectMappings,
69             ChangeLogsHelper changeLogsHelper,
70             ChangeLogsRequestHelper changeLogsRequestHelper,
71             HealthDataCategoryPriorityHelper priorityHelper,
72             PreferenceHelper preferenceHelper,
73             Clock clock,
74             BackupRestoreLogger backupRestoreLogger) {
75         mTransactionManager = transactionManager;
76         mPriorityHelper = priorityHelper;
77         mPreferenceHelper = preferenceHelper;
78         mAppInfoHelper = appInfoHelper;
79         mDatabaseHelper =
80                 new CloudBackupDatabaseHelper(
81                         transactionManager,
82                         fitnessRecordReadHelper,
83                         appInfoHelper,
84                         healthConnectMappings,
85                         internalHealthConnectMappings,
86                         changeLogsHelper,
87                         changeLogsRequestHelper,
88                         priorityHelper,
89                         preferenceHelper);
90         mClock = clock;
91         mBackupRestoreLogger = backupRestoreLogger;
92     }
93 
94     /**
95      * The changeToken returned by the previous call should be passed in to resume the upload. A
96      * null or empty changeToken means we are doing a fresh backup, and should start from the
97      * beginning.
98      *
99      * <p>If the changeToken is not valid, it means that HealthConnect can no longer resume the
100      * backup from this point, and will respond with an IllegalArgumentException. The caller should
101      * restart the backup in this case.
102      *
103      * <p>If no changes are returned by the API, this means that the client has synced all changes
104      * as of now.
105      */
106     @NonNull
getChangesForBackup(@ullable String changeToken)107     public GetChangesForBackupResponse getChangesForBackup(@Nullable String changeToken) {
108         try {
109             if (changeToken == null) {
110                 return mDatabaseHelper.getChangesAndTokenFromDataTables();
111             }
112             BackupChangeTokenHelper.BackupChangeToken backupChangeToken =
113                     BackupChangeTokenHelper.getBackupChangeToken(mTransactionManager, changeToken);
114             if (!mDatabaseHelper.isChangeLogsTokenValid(
115                     backupChangeToken.getChangeLogsRequestToken())) {
116                 throw new IllegalArgumentException("Change token invalid");
117             }
118             var recordType = backupChangeToken.getRecordType();
119             if (recordType != RECORD_TYPE_UNKNOWN) {
120                 return mDatabaseHelper.getChangesAndTokenFromDataTables(
121                         recordType,
122                         backupChangeToken.getDataTablePageToken(),
123                         backupChangeToken.getChangeLogsRequestToken());
124             }
125             return mDatabaseHelper.getIncrementalChanges(
126                     backupChangeToken.getChangeLogsRequestToken());
127         } catch (SQLiteException exception) {
128             Slog.e(TAG, "Failed to read or write to database", exception);
129             throw exception;
130         } catch (IllegalStateException exception) {
131             // This case is impossible because the database enforces uuid's non-nullity but
132             // within the RecordInternal class this is defined as nullable.
133             Slog.e(TAG, "Missing uuid for record", exception);
134             throw exception;
135         }
136     }
137 
138     /** Returns all user settings bundled as a single byte array. */
139     @NonNull
getSettingsForBackup()140     public GetLatestMetadataForBackupResponse getSettingsForBackup() {
141         Slog.i(TAG, "Formatting user settings for export.");
142         CloudBackupSettingsHelper cloudBackupSettingsHelper =
143                 new CloudBackupSettingsHelper(mPriorityHelper, mPreferenceHelper, mAppInfoHelper);
144 
145         byte[] data = cloudBackupSettingsHelper.collectUserSettings().toByteArray();
146 
147         return new GetLatestMetadataForBackupResponse(PROTO_VERSION, new BackupMetadata(data));
148     }
149 }
150