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