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 17 package com.android.server.healthconnect.storage.datatypehelpers; 18 19 import static com.android.server.healthconnect.storage.HealthConnectDatabase.createTable; 20 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.PRIMARY_COLUMN_NAME; 21 import static com.android.server.healthconnect.storage.utils.StorageUtils.INTEGER; 22 import static com.android.server.healthconnect.storage.utils.StorageUtils.INTEGER_NOT_NULL; 23 import static com.android.server.healthconnect.storage.utils.StorageUtils.PRIMARY; 24 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NULL; 25 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorInt; 26 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorLong; 27 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorString; 28 import static com.android.server.healthconnect.storage.utils.WhereClauses.LogicalOperator.AND; 29 30 import android.annotation.Nullable; 31 import android.content.ContentValues; 32 import android.database.Cursor; 33 import android.database.sqlite.SQLiteDatabase; 34 import android.health.connect.datatypes.RecordTypeIdentifier; 35 import android.util.Pair; 36 37 import com.android.server.healthconnect.storage.TransactionManager; 38 import com.android.server.healthconnect.storage.request.CreateTableRequest; 39 import com.android.server.healthconnect.storage.request.ReadTableRequest; 40 import com.android.server.healthconnect.storage.request.UpsertTableRequest; 41 import com.android.server.healthconnect.storage.utils.WhereClauses; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 46 /** 47 * A class to interact with the DB table that stores the information about the back up requests i.e. 48 * {@code TABLE_NAME} 49 * 50 * <p>This class returns the row_id of the backup_token_table as a token, that can later be used to 51 * recreate the backup token. 52 * 53 * @hide 54 */ 55 public class BackupChangeTokenHelper { 56 private static final String TABLE_NAME = "backup_change_token_table"; 57 private static final String RECORD_TYPE_COLUMN_NAME = "record_type"; 58 private static final String DATA_TABLE_PAGE_TOKEN_COLUMN_NAME = "data_table_page_token"; 59 private static final String CHANGE_LOGS_REQUEST_TOKEN_COLUMN_NAME = "change_logs_request_token"; 60 61 /** 62 * @return the row Id for the backup_change_token_table. 63 */ getBackupChangeTokenRowId( TransactionManager transactionManager, @RecordTypeIdentifier.RecordType int recordType, long dataTablePageToken, @Nullable String changeLogsRequestToken)64 public static String getBackupChangeTokenRowId( 65 TransactionManager transactionManager, 66 @RecordTypeIdentifier.RecordType int recordType, 67 long dataTablePageToken, 68 @Nullable String changeLogsRequestToken) { 69 ContentValues contentValues = new ContentValues(); 70 71 contentValues.put(RECORD_TYPE_COLUMN_NAME, recordType); 72 contentValues.put(DATA_TABLE_PAGE_TOKEN_COLUMN_NAME, dataTablePageToken); 73 contentValues.put(CHANGE_LOGS_REQUEST_TOKEN_COLUMN_NAME, changeLogsRequestToken); 74 75 return String.valueOf( 76 transactionManager.insert(new UpsertTableRequest(TABLE_NAME, contentValues))); 77 } 78 79 /** Reads the database and get backup change token. */ getBackupChangeToken( TransactionManager transactionManager, String token)80 public static BackupChangeToken getBackupChangeToken( 81 TransactionManager transactionManager, String token) { 82 ReadTableRequest readTableRequest = 83 new ReadTableRequest(TABLE_NAME) 84 .setWhereClause( 85 new WhereClauses(AND) 86 .addWhereEqualsClause(PRIMARY_COLUMN_NAME, token)); 87 try (Cursor cursor = transactionManager.read(readTableRequest)) { 88 if (!cursor.moveToFirst()) { 89 throw new IllegalArgumentException("Invalid backup change token"); 90 } 91 92 return new BackupChangeToken( 93 getCursorInt(cursor, RECORD_TYPE_COLUMN_NAME), 94 getCursorLong(cursor, DATA_TABLE_PAGE_TOKEN_COLUMN_NAME), 95 getCursorString(cursor, CHANGE_LOGS_REQUEST_TOKEN_COLUMN_NAME)); 96 } 97 } 98 99 /** A class to represent the request corresponding to a backup change token. */ 100 public static class BackupChangeToken { 101 private final @RecordTypeIdentifier.RecordType int mRecordType; 102 private final long mDataTablePageToken; 103 private final @Nullable String mChangeLogsRequestToken; 104 105 /** 106 * @param recordType record type to be backed up next 107 * @param dataTablePageToken page token for the data table to be backed up 108 * @param changeLogsRequestToken row id in change logs request table to get token for change 109 * logs table 110 */ BackupChangeToken( @ecordTypeIdentifier.RecordType int recordType, long dataTablePageToken, @Nullable String changeLogsRequestToken)111 public BackupChangeToken( 112 @RecordTypeIdentifier.RecordType int recordType, 113 long dataTablePageToken, 114 @Nullable String changeLogsRequestToken) { 115 mRecordType = recordType; 116 mDataTablePageToken = dataTablePageToken; 117 mChangeLogsRequestToken = changeLogsRequestToken; 118 } 119 120 /** 121 * Returns the record type to be backed up next. 122 * 123 * <p>Set to 0 before a complete full backup or for an incremental backup. 124 */ getRecordType()125 public @RecordTypeIdentifier.RecordType int getRecordType() { 126 return mRecordType; 127 } 128 129 /** 130 * Returns the page token for the data table to be backed up if the data type is not 131 * RECORD_TYPE_UNKNOWN. 132 * 133 * <p>If the data type name is 0, returns -1. 134 */ getDataTablePageToken()135 public long getDataTablePageToken() { 136 return mDataTablePageToken; 137 } 138 139 /** 140 * Returns the row id in the change logs request table to for retrieving the token in the 141 * change log table. 142 */ getChangeLogsRequestToken()143 public @Nullable String getChangeLogsRequestToken() { 144 return mChangeLogsRequestToken; 145 } 146 } 147 148 /** Creates the backup token table. */ applyBackupTokenUpgrade(SQLiteDatabase db)149 public static void applyBackupTokenUpgrade(SQLiteDatabase db) { 150 createTable(db, getCreateTableRequest()); 151 } 152 153 /** 154 * @return the table name. 155 */ getTableName()156 public static String getTableName() { 157 return TABLE_NAME; 158 } 159 getMainTableName()160 protected String getMainTableName() { 161 return TABLE_NAME; 162 } 163 getCreateTableRequest()164 private static CreateTableRequest getCreateTableRequest() { 165 return new CreateTableRequest(TABLE_NAME, getColumnInfo()); 166 } 167 getColumnInfo()168 private static List<Pair<String, String>> getColumnInfo() { 169 List<Pair<String, String>> columnInfo = new ArrayList<>(); 170 columnInfo.add(new Pair<>(PRIMARY_COLUMN_NAME, PRIMARY)); 171 columnInfo.add(new Pair<>(RECORD_TYPE_COLUMN_NAME, INTEGER_NOT_NULL)); 172 columnInfo.add(new Pair<>(DATA_TABLE_PAGE_TOKEN_COLUMN_NAME, INTEGER)); 173 columnInfo.add(new Pair<>(CHANGE_LOGS_REQUEST_TOKEN_COLUMN_NAME, TEXT_NULL)); 174 return columnInfo; 175 } 176 } 177