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.request.UpsertTableRequest.TYPE_STRING; 20 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NOT_NULL_UNIQUE; 21 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NULL; 22 23 import android.annotation.Nullable; 24 import android.content.ContentValues; 25 import android.database.Cursor; 26 import android.util.Pair; 27 28 import com.android.server.healthconnect.storage.TransactionManager; 29 import com.android.server.healthconnect.storage.request.CreateTableRequest; 30 import com.android.server.healthconnect.storage.request.DeleteTableRequest; 31 import com.android.server.healthconnect.storage.request.ReadTableRequest; 32 import com.android.server.healthconnect.storage.request.UpsertTableRequest; 33 import com.android.server.healthconnect.storage.utils.StorageUtils; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.concurrent.ConcurrentHashMap; 41 42 /** 43 * A helper class to store user preferences, set in UI APK for the platform. 44 * 45 * @hide 46 */ 47 // TODO(b/303023796): Make this final. 48 public class PreferenceHelper extends DatabaseHelper { 49 private static final String TAG = "PreferenceHelper"; 50 private static final String TABLE_NAME = "preference_table"; 51 private static final String KEY_COLUMN_NAME = "key"; 52 public static final List<Pair<String, Integer>> UNIQUE_COLUMN_INFO = 53 Collections.singletonList(new Pair<>(KEY_COLUMN_NAME, TYPE_STRING)); 54 private static final String VALUE_COLUMN_NAME = "value"; 55 private final TransactionManager mTransactionManager; 56 57 /** 58 * Key to store timestamp of the last time any PHR <b>read medical resources</b> API is called. 59 */ 60 private static final String PREFS_KEY_PHR_LAST_READ_MEDICAL_RESOURCES_API = 61 "phr_last_read_medical_resources_api"; 62 63 protected volatile ConcurrentHashMap<String, String> mPreferences; 64 65 @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression PreferenceHelper( TransactionManager transactionManager, DatabaseHelpers databaseHelpers)66 public PreferenceHelper( 67 TransactionManager transactionManager, DatabaseHelpers databaseHelpers) { 68 super(databaseHelpers); 69 mTransactionManager = transactionManager; 70 } 71 72 /** Note: Overrides existing preference (if it exists) with the new value */ insertOrReplacePreference(String key, String value)73 public synchronized void insertOrReplacePreference(String key, String value) { 74 mTransactionManager.insertOrReplaceOnConflict( 75 new UpsertTableRequest( 76 TABLE_NAME, getContentValues(key, value), UNIQUE_COLUMN_INFO)); 77 getPreferences().put(key, value); 78 } 79 80 /** Removes key entry from the table */ removeKey(String id)81 public synchronized void removeKey(String id) { 82 mTransactionManager.delete(new DeleteTableRequest(TABLE_NAME).setId(KEY_COLUMN_NAME, id)); 83 getPreferences().remove(id); 84 } 85 86 /** Inserts multiple preferences together in a transaction */ insertOrReplacePreferencesTransaction( HashMap<String, String> keyValues)87 public synchronized void insertOrReplacePreferencesTransaction( 88 HashMap<String, String> keyValues) { 89 List<UpsertTableRequest> requests = new ArrayList<>(); 90 keyValues.forEach( 91 (key, value) -> 92 requests.add( 93 new UpsertTableRequest( 94 TABLE_NAME, 95 getContentValues(key, value), 96 UNIQUE_COLUMN_INFO))); 97 mTransactionManager.insertOrReplaceAllOnConflict(requests); 98 getPreferences().putAll(keyValues); 99 } 100 getCreateTableRequest()101 public static CreateTableRequest getCreateTableRequest() { 102 return new CreateTableRequest(TABLE_NAME, getColumnInfo()); 103 } 104 105 @Nullable getPreference(String key)106 public String getPreference(String key) { 107 return getPreferences().get(key); 108 } 109 110 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 111 @Override clearCache()112 public synchronized void clearCache() { 113 mPreferences = null; 114 } 115 116 @Override getMainTableName()117 protected String getMainTableName() { 118 return TABLE_NAME; 119 } 120 121 /** Fetch preferences into memory. */ initializePreferences()122 public void initializePreferences() { 123 populatePreferences(); 124 } 125 getPreferences()126 protected Map<String, String> getPreferences() { 127 if (mPreferences == null) { 128 populatePreferences(); 129 } 130 return mPreferences; 131 } 132 getContentValues(String key, String value)133 private ContentValues getContentValues(String key, String value) { 134 ContentValues contentValues = new ContentValues(); 135 contentValues.put(KEY_COLUMN_NAME, key); 136 contentValues.put(VALUE_COLUMN_NAME, value); 137 return contentValues; 138 } 139 populatePreferences()140 private synchronized void populatePreferences() { 141 if (mPreferences != null) { 142 return; 143 } 144 145 mPreferences = new ConcurrentHashMap<>(); 146 try (Cursor cursor = mTransactionManager.read(new ReadTableRequest(TABLE_NAME))) { 147 while (cursor.moveToNext()) { 148 String key = StorageUtils.getCursorString(cursor, KEY_COLUMN_NAME); 149 String value = StorageUtils.getCursorString(cursor, VALUE_COLUMN_NAME); 150 mPreferences.put(key, value); 151 } 152 } 153 } 154 getColumnInfo()155 private static List<Pair<String, String>> getColumnInfo() { 156 ArrayList<Pair<String, String>> columnInfo = new ArrayList<>(); 157 columnInfo.add(new Pair<>(KEY_COLUMN_NAME, TEXT_NOT_NULL_UNIQUE)); 158 columnInfo.add(new Pair<>(VALUE_COLUMN_NAME, TEXT_NULL)); 159 160 return columnInfo; 161 } 162 } 163