• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.healthfitness.flags.AconfigFlagHelper.isCloudBackupRestoreEnabled;
20 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.PRIMARY_COLUMN_NAME;
21 import static com.android.server.healthconnect.storage.utils.StorageUtils.DELIMITER;
22 import static com.android.server.healthconnect.storage.utils.StorageUtils.INTEGER;
23 import static com.android.server.healthconnect.storage.utils.StorageUtils.PRIMARY;
24 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NOT_NULL;
25 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NULL;
26 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorInt;
27 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorIntegerList;
28 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorString;
29 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorStringList;
30 import static com.android.server.healthconnect.storage.utils.WhereClauses.LogicalOperator.AND;
31 
32 import android.content.ContentValues;
33 import android.database.Cursor;
34 import android.health.connect.changelog.ChangeLogTokenRequest;
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.DeleteTableRequest;
40 import com.android.server.healthconnect.storage.request.ReadTableRequest;
41 import com.android.server.healthconnect.storage.request.UpsertTableRequest;
42 import com.android.server.healthconnect.storage.utils.StorageUtils;
43 import com.android.server.healthconnect.storage.utils.WhereClauses;
44 
45 import java.time.Instant;
46 import java.time.temporal.ChronoUnit;
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 /**
51  * A class to interact with the DB table that stores the information about the change log requests
52  * i.e. {@code TABLE_NAME}
53  *
54  * <p>This class returns the row_id of the change_log_request_table as a token, that can later be
55  * used to recreate the request.
56  *
57  * @hide
58  */
59 public final class ChangeLogsRequestHelper extends DatabaseHelper {
60     public static final String TABLE_NAME = "change_log_request_table";
61     static final int DEFAULT_CHANGE_LOG_TIME_PERIOD_IN_DAYS = 32;
62     static final int NEW_CHANGE_LOG_TIME_PERIOD_IN_DAYS = 90;
63     private static final String PACKAGES_TO_FILTERS_COLUMN_NAME = "packages_to_filter";
64     private static final String RECORD_TYPES_COLUMN_NAME = "record_types";
65     private static final String PACKAGE_NAME_COLUMN_NAME = "package_name";
66     private static final String ROW_ID_CHANGE_LOGS_TABLE_COLUMN_NAME = "row_id_change_logs_table";
67     private static final String TIME_COLUMN_NAME = "time";
68 
69     private final TransactionManager mTransactionManager;
70 
ChangeLogsRequestHelper( TransactionManager transactionManager, DatabaseHelpers databaseHelpers)71     public ChangeLogsRequestHelper(
72             TransactionManager transactionManager, DatabaseHelpers databaseHelpers) {
73         super(databaseHelpers);
74         mTransactionManager = transactionManager;
75     }
76 
77     @Override
getMainTableName()78     protected String getMainTableName() {
79         return TABLE_NAME;
80     }
81 
getCreateTableRequest()82     public static CreateTableRequest getCreateTableRequest() {
83         return new CreateTableRequest(TABLE_NAME, getColumnInfo());
84     }
85 
getToken( long latestChangeLogRowId, String packageName, ChangeLogTokenRequest request)86     public String getToken(
87             long latestChangeLogRowId, String packageName, ChangeLogTokenRequest request) {
88         ContentValues contentValues = new ContentValues();
89 
90         /**
91          * Store package names here as a package name and not as {@link AppInfoHelper.AppInfo#mId}
92          * as ID might not be available right now but might become available when the actual request
93          * for this token comes
94          */
95         contentValues.put(
96                 PACKAGES_TO_FILTERS_COLUMN_NAME,
97                 String.join(DELIMITER, request.getPackageNamesToFilter()));
98         contentValues.put(
99                 RECORD_TYPES_COLUMN_NAME,
100                 StorageUtils.flattenIntArray(request.getRecordTypesArray()));
101         contentValues.put(PACKAGE_NAME_COLUMN_NAME, packageName);
102         contentValues.put(ROW_ID_CHANGE_LOGS_TABLE_COLUMN_NAME, latestChangeLogRowId);
103         contentValues.put(TIME_COLUMN_NAME, Instant.now().toEpochMilli());
104 
105         return String.valueOf(
106                 mTransactionManager.insert(new UpsertTableRequest(TABLE_NAME, contentValues)));
107     }
108 
getRequest(String packageName, String token)109     public TokenRequest getRequest(String packageName, String token) {
110         ReadTableRequest readTableRequest =
111                 new ReadTableRequest(TABLE_NAME)
112                         .setWhereClause(
113                                 new WhereClauses(AND)
114                                         .addWhereEqualsClause(PRIMARY_COLUMN_NAME, token)
115                                         .addWhereEqualsClause(
116                                                 PACKAGE_NAME_COLUMN_NAME, packageName));
117         try (Cursor cursor = mTransactionManager.read(readTableRequest)) {
118             if (!cursor.moveToFirst()) {
119                 throw new IllegalArgumentException("Invalid token");
120             }
121 
122             return new TokenRequest(
123                     getCursorStringList(cursor, PACKAGES_TO_FILTERS_COLUMN_NAME, DELIMITER),
124                     getCursorIntegerList(cursor, RECORD_TYPES_COLUMN_NAME, DELIMITER),
125                     getCursorString(cursor, PACKAGE_NAME_COLUMN_NAME),
126                     getCursorInt(cursor, ROW_ID_CHANGE_LOGS_TABLE_COLUMN_NAME));
127         }
128     }
129 
getNextPageToken(TokenRequest changeLogTokenRequest, long nextRowId)130     public String getNextPageToken(TokenRequest changeLogTokenRequest, long nextRowId) {
131         ContentValues contentValues = new ContentValues();
132         contentValues.put(
133                 PACKAGES_TO_FILTERS_COLUMN_NAME,
134                 String.join(DELIMITER, changeLogTokenRequest.getPackageNamesToFilter()));
135         contentValues.put(
136                 RECORD_TYPES_COLUMN_NAME,
137                 StorageUtils.flattenIntList(changeLogTokenRequest.getRecordTypes()));
138         contentValues.put(
139                 PACKAGE_NAME_COLUMN_NAME, changeLogTokenRequest.getRequestingPackageName());
140         contentValues.put(ROW_ID_CHANGE_LOGS_TABLE_COLUMN_NAME, nextRowId);
141 
142         return String.valueOf(
143                 mTransactionManager.insert(new UpsertTableRequest(TABLE_NAME, contentValues)));
144     }
145 
getDeleteRequestForAutoDelete()146     public static DeleteTableRequest getDeleteRequestForAutoDelete() {
147         int changeLogTimePeriod =
148                 isCloudBackupRestoreEnabled()
149                         ? NEW_CHANGE_LOG_TIME_PERIOD_IN_DAYS
150                         : DEFAULT_CHANGE_LOG_TIME_PERIOD_IN_DAYS;
151         return new DeleteTableRequest(TABLE_NAME)
152                 .setTimeFilter(
153                         TIME_COLUMN_NAME,
154                         Instant.EPOCH.toEpochMilli(),
155                         Instant.now().minus(changeLogTimePeriod, ChronoUnit.DAYS).toEpochMilli());
156     }
157 
getColumnInfo()158     private static List<Pair<String, String>> getColumnInfo() {
159         List<Pair<String, String>> columnInfo = new ArrayList<>();
160         columnInfo.add(new Pair<>(PRIMARY_COLUMN_NAME, PRIMARY));
161         columnInfo.add(new Pair<>(PACKAGES_TO_FILTERS_COLUMN_NAME, TEXT_NOT_NULL));
162         columnInfo.add(new Pair<>(PACKAGE_NAME_COLUMN_NAME, TEXT_NOT_NULL));
163         columnInfo.add(new Pair<>(RECORD_TYPES_COLUMN_NAME, TEXT_NULL));
164         columnInfo.add(new Pair<>(ROW_ID_CHANGE_LOGS_TABLE_COLUMN_NAME, INTEGER));
165         columnInfo.add(new Pair<>(TIME_COLUMN_NAME, INTEGER));
166 
167         return columnInfo;
168     }
169 
170     /** A class to represent the request corresponding to a token */
171     public static final class TokenRequest {
172         private final List<String> mPackageNamesToFilter;
173         private final List<Integer> mRecordTypes;
174         private final String mRequestingPackageName;
175         private final long mRowIdChangeLogs;
176 
177         /**
178          * @param requestingPackageName contributing package name
179          * @param packageNamesToFilter package names to filter
180          * @param recordTypes records to filter
181          * @param rowIdChangeLogs row id of change log table after which the logs are to be fetched
182          */
TokenRequest( List<String> packageNamesToFilter, List<Integer> recordTypes, String requestingPackageName, long rowIdChangeLogs)183         public TokenRequest(
184                 List<String> packageNamesToFilter,
185                 List<Integer> recordTypes,
186                 String requestingPackageName,
187                 long rowIdChangeLogs) {
188             mPackageNamesToFilter = packageNamesToFilter;
189             mRecordTypes = recordTypes;
190             mRequestingPackageName = requestingPackageName;
191             mRowIdChangeLogs = rowIdChangeLogs;
192         }
193 
getRowIdChangeLogs()194         public long getRowIdChangeLogs() {
195             return mRowIdChangeLogs;
196         }
197 
getRequestingPackageName()198         public String getRequestingPackageName() {
199             return mRequestingPackageName;
200         }
201 
getPackageNamesToFilter()202         public List<String> getPackageNamesToFilter() {
203             return mPackageNamesToFilter;
204         }
205 
getRecordTypes()206         public List<Integer> getRecordTypes() {
207             return mRecordTypes;
208         }
209     }
210 }
211