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