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