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