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.server.healthconnect.storage.datatypehelpers.InstantRecordHelper.TIME_COLUMN_NAME; 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_NOT_NULL; 23 import static com.android.server.healthconnect.storage.utils.StorageUtils.PRIMARY_AUTOINCREMENT; 24 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NOT_NULL; 25 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorInt; 26 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorIntegerList; 27 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorLong; 28 29 import android.annotation.NonNull; 30 import android.content.ContentValues; 31 import android.database.Cursor; 32 import android.database.sqlite.SQLiteDatabase; 33 import android.health.connect.accesslog.AccessLog; 34 import android.health.connect.accesslog.AccessLog.OperationType; 35 import android.health.connect.datatypes.RecordTypeIdentifier; 36 import android.util.Pair; 37 38 import com.android.server.healthconnect.storage.TransactionManager; 39 import com.android.server.healthconnect.storage.request.CreateTableRequest; 40 import com.android.server.healthconnect.storage.request.DeleteTableRequest; 41 import com.android.server.healthconnect.storage.request.ReadTableRequest; 42 import com.android.server.healthconnect.storage.request.UpsertTableRequest; 43 44 import java.time.Instant; 45 import java.time.temporal.ChronoUnit; 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.stream.Collectors; 49 50 /** 51 * A helper class to fetch and store the access logs. 52 * 53 * @hide 54 */ 55 public final class AccessLogsHelper { 56 public static final String TABLE_NAME = "access_logs_table"; 57 private static final String RECORD_TYPE_COLUMN_NAME = "record_type"; 58 private static final String APP_ID_COLUMN_NAME = "app_id"; 59 private static final String ACCESS_TIME_COLUMN_NAME = "access_time"; 60 private static final String OPERATION_TYPE_COLUMN_NAME = "operation_type"; 61 private static final int NUM_COLS = 5; 62 private static final int DEFAULT_ACCESS_LOG_TIME_PERIOD_IN_DAYS = 7; 63 private static volatile AccessLogsHelper sAccessLogsHelper; 64 AccessLogsHelper()65 private AccessLogsHelper() {} 66 67 @NonNull getCreateTableRequest()68 public CreateTableRequest getCreateTableRequest() { 69 return new CreateTableRequest(TABLE_NAME, getColumnInfo()); 70 } 71 72 /** 73 * @return AccessLog list 74 */ queryAccessLogs()75 public List<AccessLog> queryAccessLogs() { 76 final ReadTableRequest readTableRequest = new ReadTableRequest(TABLE_NAME); 77 78 List<AccessLog> accessLogsList = new ArrayList<>(); 79 final AppInfoHelper appInfoHelper = AppInfoHelper.getInstance(); 80 final TransactionManager transactionManager = TransactionManager.getInitialisedInstance(); 81 try (Cursor cursor = transactionManager.read(readTableRequest)) { 82 while (cursor.moveToNext()) { 83 String packageName = 84 String.valueOf( 85 appInfoHelper.getPackageName( 86 getCursorLong(cursor, APP_ID_COLUMN_NAME))); 87 @RecordTypeIdentifier.RecordType 88 List<Integer> recordTypes = 89 getCursorIntegerList(cursor, RECORD_TYPE_COLUMN_NAME, DELIMITER); 90 long accessTime = getCursorLong(cursor, ACCESS_TIME_COLUMN_NAME); 91 @OperationType.OperationTypes 92 int operationType = getCursorInt(cursor, OPERATION_TYPE_COLUMN_NAME); 93 accessLogsList.add( 94 new AccessLog(packageName, recordTypes, accessTime, operationType)); 95 } 96 } 97 98 return accessLogsList; 99 } 100 101 /** Adds an entry in to the access logs table for every insert or read operation request */ addAccessLog( String packageName, @RecordTypeIdentifier.RecordType List<Integer> recordTypeList, @OperationType.OperationTypes int operationType)102 public void addAccessLog( 103 String packageName, 104 @RecordTypeIdentifier.RecordType List<Integer> recordTypeList, 105 @OperationType.OperationTypes int operationType) { 106 UpsertTableRequest request = 107 getUpsertTableRequest(packageName, recordTypeList, operationType); 108 TransactionManager.getInitialisedInstance().insert(request); 109 } 110 111 @NonNull getUpsertTableRequest( String packageName, List<Integer> recordTypeList, int operationType)112 public UpsertTableRequest getUpsertTableRequest( 113 String packageName, List<Integer> recordTypeList, int operationType) { 114 ContentValues contentValues = new ContentValues(); 115 contentValues.put( 116 RECORD_TYPE_COLUMN_NAME, 117 recordTypeList.stream().map(String::valueOf).collect(Collectors.joining(","))); 118 contentValues.put( 119 APP_ID_COLUMN_NAME, AppInfoHelper.getInstance().getAppInfoId(packageName)); 120 contentValues.put(ACCESS_TIME_COLUMN_NAME, Instant.now().toEpochMilli()); 121 contentValues.put(OPERATION_TYPE_COLUMN_NAME, operationType); 122 123 return new UpsertTableRequest(TABLE_NAME, contentValues); 124 } 125 126 /** 127 * Returns an instance of {@link DeleteTableRequest} to delete entries in access logs table 128 * older than a week. 129 */ getDeleteRequestForAutoDelete()130 public DeleteTableRequest getDeleteRequestForAutoDelete() { 131 return new DeleteTableRequest(TABLE_NAME) 132 .setTimeFilter( 133 TIME_COLUMN_NAME, 134 Instant.EPOCH.toEpochMilli(), 135 Instant.now() 136 .minus(DEFAULT_ACCESS_LOG_TIME_PERIOD_IN_DAYS, ChronoUnit.DAYS) 137 .toEpochMilli()); 138 } 139 140 @NonNull getColumnInfo()141 private List<Pair<String, String>> getColumnInfo() { 142 List<Pair<String, String>> columnInfo = new ArrayList<>(NUM_COLS); 143 columnInfo.add(new Pair<>(PRIMARY_COLUMN_NAME, PRIMARY_AUTOINCREMENT)); 144 columnInfo.add(new Pair<>(APP_ID_COLUMN_NAME, INTEGER_NOT_NULL)); 145 columnInfo.add(new Pair<>(RECORD_TYPE_COLUMN_NAME, TEXT_NOT_NULL)); 146 columnInfo.add(new Pair<>(ACCESS_TIME_COLUMN_NAME, INTEGER_NOT_NULL)); 147 columnInfo.add(new Pair<>(OPERATION_TYPE_COLUMN_NAME, INTEGER_NOT_NULL)); 148 149 return columnInfo; 150 } 151 onUpgrade(int oldVersion, int newVersion, SQLiteDatabase db)152 public void onUpgrade(int oldVersion, int newVersion, SQLiteDatabase db) {} 153 getInstance()154 public static synchronized AccessLogsHelper getInstance() { 155 if (sAccessLogsHelper == null) { 156 sAccessLogsHelper = new AccessLogsHelper(); 157 } 158 159 return sAccessLogsHelper; 160 } 161 } 162