• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.health.connect.Constants.DEFAULT_LONG;
20 
21 import static com.android.server.healthconnect.storage.utils.StorageUtils.INTEGER;
22 import static com.android.server.healthconnect.storage.utils.StorageUtils.PRIMARY;
23 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NULL;
24 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorInt;
25 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorLong;
26 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorString;
27 
28 import android.annotation.NonNull;
29 import android.content.ContentValues;
30 import android.database.Cursor;
31 import android.database.sqlite.SQLiteDatabase;
32 import android.health.connect.datatypes.Device.DeviceType;
33 import android.health.connect.internal.datatypes.RecordInternal;
34 import android.util.Pair;
35 
36 import com.android.server.healthconnect.storage.TransactionManager;
37 import com.android.server.healthconnect.storage.request.CreateTableRequest;
38 import com.android.server.healthconnect.storage.request.ReadTableRequest;
39 import com.android.server.healthconnect.storage.request.UpsertTableRequest;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Objects;
45 import java.util.concurrent.ConcurrentHashMap;
46 
47 /**
48  * A class to help with the DB transaction for storing Device Info. {@link DeviceInfoHelper} acts as
49  * a layer b/w the device_info_table stored in the DB and helps perform insert and read operations
50  * on the table
51  *
52  * @hide
53  */
54 public class DeviceInfoHelper {
55     private static final String TABLE_NAME = "device_info_table";
56     private static final String MANUFACTURER_COLUMN_NAME = "manufacturer";
57     private static final String MODEL_COLUMN_NAME = "model";
58     private static final String DEVICE_TYPE_COLUMN_NAME = "device_type";
59     private static volatile DeviceInfoHelper sDeviceInfoHelper;
60     /** Map to store deviceInfoId -> DeviceInfo mapping for populating record for read */
61     private volatile ConcurrentHashMap<Long, DeviceInfo> mIdDeviceInfoMap;
62     /** ArrayMap to store DeviceInfo -> rowId mapping (model,manufacturer,device_type -> rowId) */
63     private volatile ConcurrentHashMap<DeviceInfo, Long> mDeviceInfoMap;
64 
65     /**
66      * Returns a requests representing the tables that should be created corresponding to this
67      * helper
68      */
69     @NonNull
getCreateTableRequest()70     public final CreateTableRequest getCreateTableRequest() {
71         return new CreateTableRequest(TABLE_NAME, getColumnInfo());
72     }
73 
getTableName()74     public String getTableName() {
75         return TABLE_NAME;
76     }
77 
78     /** Populates record with deviceInfoId */
populateDeviceInfoId(@onNull RecordInternal<?> recordInternal)79     public void populateDeviceInfoId(@NonNull RecordInternal<?> recordInternal) {
80         String manufacturer = recordInternal.getManufacturer();
81         String model = recordInternal.getModel();
82         int deviceType = recordInternal.getDeviceType();
83         DeviceInfo deviceInfo = new DeviceInfo(manufacturer, model, deviceType);
84         long rowId = getDeviceInfoMap().getOrDefault(deviceInfo, DEFAULT_LONG);
85         if (rowId == DEFAULT_LONG) {
86             rowId = insertIfNotPresent(deviceInfo);
87         }
88         recordInternal.setDeviceInfoId(rowId);
89     }
90 
91     /**
92      * Populates record with manufacturer, model and deviceType values
93      *
94      * @param deviceInfoId rowId from {@code device_info_table }
95      * @param record The record to be populated with values
96      */
populateRecordWithValue(long deviceInfoId, @NonNull RecordInternal<?> record)97     public void populateRecordWithValue(long deviceInfoId, @NonNull RecordInternal<?> record) {
98         DeviceInfo deviceInfo = getIdDeviceInfoMap().get(deviceInfoId);
99         if (deviceInfo != null) {
100             record.setDeviceType(deviceInfo.mDeviceType);
101             record.setManufacturer(deviceInfo.mManufacturer);
102             record.setModel(deviceInfo.mModel);
103         }
104     }
105 
106     // Called on DB update.
onUpgrade(int oldVersion, int newVersion, @NonNull SQLiteDatabase db)107     public void onUpgrade(int oldVersion, int newVersion, @NonNull SQLiteDatabase db) {
108         // empty by default
109     }
110 
clearCache()111     public synchronized void clearCache() {
112         mDeviceInfoMap = null;
113         mIdDeviceInfoMap = null;
114     }
115 
populateDeviceInfoMap()116     private synchronized void populateDeviceInfoMap() {
117         if (mDeviceInfoMap != null) {
118             return;
119         }
120 
121         ConcurrentHashMap<DeviceInfo, Long> deviceInfoMap = new ConcurrentHashMap<>();
122         ConcurrentHashMap<Long, DeviceInfo> idDeviceInfoMap = new ConcurrentHashMap<>();
123         final TransactionManager transactionManager = TransactionManager.getInitialisedInstance();
124         try (Cursor cursor = transactionManager.read(new ReadTableRequest(TABLE_NAME))) {
125             while (cursor.moveToNext()) {
126                 long rowId = getCursorLong(cursor, RecordHelper.PRIMARY_COLUMN_NAME);
127                 String manufacturer = getCursorString(cursor, MANUFACTURER_COLUMN_NAME);
128                 String model = getCursorString(cursor, MODEL_COLUMN_NAME);
129                 int deviceType = getCursorInt(cursor, DEVICE_TYPE_COLUMN_NAME);
130                 DeviceInfo deviceInfo = new DeviceInfo(manufacturer, model, deviceType);
131                 deviceInfoMap.put(deviceInfo, rowId);
132                 idDeviceInfoMap.put(rowId, deviceInfo);
133             }
134         }
135 
136         mDeviceInfoMap = deviceInfoMap;
137         mIdDeviceInfoMap = idDeviceInfoMap;
138     }
139 
getIdDeviceInfoMap()140     private Map<Long, DeviceInfo> getIdDeviceInfoMap() {
141         if (mIdDeviceInfoMap == null) {
142             populateDeviceInfoMap();
143         }
144         return mIdDeviceInfoMap;
145     }
146 
getDeviceInfoMap()147     private Map<DeviceInfo, Long> getDeviceInfoMap() {
148         if (mDeviceInfoMap == null) {
149             populateDeviceInfoMap();
150         }
151 
152         return mDeviceInfoMap;
153     }
154 
insertIfNotPresent(DeviceInfo deviceInfo)155     private synchronized long insertIfNotPresent(DeviceInfo deviceInfo) {
156         Long currentRowId = getDeviceInfoMap().get(deviceInfo);
157         if (currentRowId != null) {
158             return currentRowId;
159         }
160 
161         long rowId =
162                 TransactionManager.getInitialisedInstance()
163                         .insert(
164                                 new UpsertTableRequest(
165                                         TABLE_NAME,
166                                         getContentValues(
167                                                 deviceInfo.mManufacturer,
168                                                 deviceInfo.mModel,
169                                                 deviceInfo.mDeviceType)));
170         getDeviceInfoMap().put(deviceInfo, rowId);
171         getIdDeviceInfoMap().put(rowId, deviceInfo);
172         return rowId;
173     }
174 
175     @NonNull
getContentValues(String manufacturer, String model, int deviceType)176     private ContentValues getContentValues(String manufacturer, String model, int deviceType) {
177         ContentValues contentValues = new ContentValues();
178 
179         contentValues.put(MANUFACTURER_COLUMN_NAME, manufacturer);
180         contentValues.put(MODEL_COLUMN_NAME, model);
181         contentValues.put(DEVICE_TYPE_COLUMN_NAME, deviceType);
182 
183         return contentValues;
184     }
185 
186     /**
187      * This implementation should return the column names with which the table should be created.
188      *
189      * <p>NOTE: New columns can only be added via onUpgrade. Why? Consider what happens if a table
190      * already exists on the device
191      *
192      * <p>PLEASE DON'T USE THIS METHOD TO ADD NEW COLUMNS
193      */
194     @NonNull
getColumnInfo()195     private List<Pair<String, String>> getColumnInfo() {
196         ArrayList<Pair<String, String>> columnInfo = new ArrayList<>();
197         columnInfo.add(new Pair<>(RecordHelper.PRIMARY_COLUMN_NAME, PRIMARY));
198         columnInfo.add(new Pair<>(MANUFACTURER_COLUMN_NAME, TEXT_NULL));
199         columnInfo.add(new Pair<>(MODEL_COLUMN_NAME, TEXT_NULL));
200         columnInfo.add(new Pair<>(DEVICE_TYPE_COLUMN_NAME, INTEGER));
201 
202         return columnInfo;
203     }
204 
getInstance()205     public static synchronized DeviceInfoHelper getInstance() {
206         if (sDeviceInfoHelper == null) {
207             sDeviceInfoHelper = new DeviceInfoHelper();
208         }
209         return sDeviceInfoHelper;
210     }
211 
212     private static final class DeviceInfo {
213         private final String mManufacturer;
214         private final String mModel;
215         @DeviceType private final int mDeviceType;
216 
DeviceInfo(String manufacturer, String model, @DeviceType int deviceType)217         DeviceInfo(String manufacturer, String model, @DeviceType int deviceType) {
218             mManufacturer = manufacturer;
219             mModel = model;
220             mDeviceType = deviceType;
221         }
222 
223         @Override
hashCode()224         public int hashCode() {
225             int result = mManufacturer != null ? mManufacturer.hashCode() : 0;
226             result = 31 * result + (mModel != null ? mModel.hashCode() : 0) + mDeviceType;
227             return result;
228         }
229 
230         @Override
equals(Object o)231         public boolean equals(Object o) {
232             if (Objects.isNull(o)) {
233                 return false;
234             }
235             if (this == o) {
236                 return true;
237             }
238             if (getClass() != o.getClass()) {
239                 return false;
240             }
241 
242             DeviceInfo deviceInfo = (DeviceInfo) o;
243             if (!Objects.equals(mManufacturer, deviceInfo.mManufacturer)) {
244                 return false;
245             }
246             if (!Objects.equals(mModel, deviceInfo.mModel)) {
247                 return false;
248             }
249 
250             return mDeviceType == deviceInfo.mDeviceType;
251         }
252     }
253 }
254