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