Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 | package android.car.usb.handler; |
| 17 | |
| 18 | import android.annotation.Nullable; |
| 19 | import android.content.ComponentName; |
| 20 | import android.content.ContentValues; |
| 21 | import android.content.Context; |
| 22 | import android.database.Cursor; |
| 23 | import android.database.sqlite.SQLiteDatabase; |
| 24 | import android.database.sqlite.SQLiteOpenHelper; |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 25 | import android.hardware.usb.UsbDevice; |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 26 | import android.util.Log; |
Pavel Maltsev | 2e33ff5 | 2018-11-16 13:47:53 -0800 | [diff] [blame] | 27 | |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 28 | import java.util.ArrayList; |
| 29 | import java.util.List; |
| 30 | |
| 31 | /** |
| 32 | * Provides API to persist USB device settings. |
| 33 | */ |
| 34 | public final class UsbSettingsStorage { |
| 35 | private static final String TAG = UsbSettingsStorage.class.getSimpleName(); |
| 36 | |
| 37 | private static final String TABLE_USB_SETTINGS = "usb_devices"; |
| 38 | private static final String COLUMN_SERIAL = "serial"; |
| 39 | private static final String COLUMN_VID = "vid"; |
| 40 | private static final String COLUMN_PID = "pid"; |
| 41 | private static final String COLUMN_NAME = "name"; |
| 42 | private static final String COLUMN_HANDLER = "handler"; |
| 43 | private static final String COLUMN_AOAP = "aoap"; |
| 44 | private static final String COLUMN_DEFAULT_HANDLER = "default_handler"; |
| 45 | |
| 46 | private final UsbSettingsDbHelper mDbHelper; |
| 47 | |
| 48 | public UsbSettingsStorage(Context context) { |
| 49 | mDbHelper = new UsbSettingsDbHelper(context); |
| 50 | } |
| 51 | |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 52 | private Cursor queryFor(SQLiteDatabase db, UsbDevice device) { |
| 53 | String serial = device.getSerialNumber(); |
| 54 | String selection; |
Pavel Maltsev | 2e33ff5 | 2018-11-16 13:47:53 -0800 | [diff] [blame] | 55 | List<String> selectionArgs = new ArrayList<>(); |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 56 | if (AoapInterface.isDeviceInAoapMode(device)) { |
| 57 | selection = COLUMN_SERIAL + " = ? AND " + COLUMN_AOAP + " = 1"; |
Pavel Maltsev | 2e33ff5 | 2018-11-16 13:47:53 -0800 | [diff] [blame] | 58 | selectionArgs.add(serial); |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 59 | } else if (serial == null) { |
Pavel Maltsev | 2e33ff5 | 2018-11-16 13:47:53 -0800 | [diff] [blame] | 60 | selection = COLUMN_SERIAL + " IS NULL"; |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 61 | } else { |
Pavel Maltsev | 2e33ff5 | 2018-11-16 13:47:53 -0800 | [diff] [blame] | 62 | selection = COLUMN_SERIAL + " = ?"; |
| 63 | selectionArgs.add(serial); |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 64 | } |
Pavel Maltsev | 2e33ff5 | 2018-11-16 13:47:53 -0800 | [diff] [blame] | 65 | |
| 66 | selection += " AND " + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?"; |
| 67 | selectionArgs.add(String.valueOf(device.getVendorId())); |
| 68 | selectionArgs.add(String.valueOf(device.getProductId())); |
| 69 | |
| 70 | return db.query(TABLE_USB_SETTINGS, null, selection, |
| 71 | selectionArgs.toArray(new String[0]), null, null, null); |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 72 | } |
| 73 | |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 74 | /** |
| 75 | * Returns settings for {@serialNumber} or null if it doesn't exist. |
| 76 | */ |
| 77 | @Nullable |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 78 | public UsbDeviceSettings getSettings(UsbDevice device) { |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 79 | try (SQLiteDatabase db = mDbHelper.getReadableDatabase(); |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 80 | Cursor resultCursor = queryFor(db, device)) { |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 81 | if (resultCursor.getCount() > 1) { |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 82 | throw new RuntimeException("Querying for device: " + device |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 83 | + " returned " + resultCursor.getCount() + " results"); |
| 84 | } |
| 85 | if (resultCursor.getCount() == 0) { |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 86 | Log.w(TAG, "Usb setting missing for device: " + device); |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 87 | return null; |
| 88 | } |
| 89 | List<UsbDeviceSettings> settings = constructSettings(resultCursor); |
| 90 | return settings.get(0); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Saves or updates settings for USB device. |
| 96 | */ |
| 97 | public void saveSettings(UsbDeviceSettings settings) { |
| 98 | try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { |
| 99 | long result = db.replace( |
| 100 | TABLE_USB_SETTINGS, |
| 101 | null, |
| 102 | settingsToContentValues(settings)); |
| 103 | if (result == -1) { |
| 104 | Log.e(TAG, "Failed to save settings: " + settings); |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Delete settings for USB device. |
| 111 | */ |
| 112 | public void deleteSettings(String serialNumber, int vid, int pid) { |
| 113 | try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { |
| 114 | int result = db.delete( |
| 115 | TABLE_USB_SETTINGS, |
| 116 | COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID |
| 117 | + " = ?", |
| 118 | new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)}); |
| 119 | if (result == 0) { |
| 120 | Log.w(TAG, "No settings with serialNumber: " + serialNumber |
| 121 | + " vid: " + vid + " pid: " + pid); |
| 122 | } |
| 123 | if (result > 1) { |
| 124 | Log.e(TAG, "Deleted multiple rows (" + result + ") for serialNumber: " |
| 125 | + serialNumber + " vid: " + vid + " pid: " + pid); |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * Returns all saved settings. |
| 132 | */ |
| 133 | public List<UsbDeviceSettings> getAllSettings() { |
| 134 | try (SQLiteDatabase db = mDbHelper.getReadableDatabase(); |
| 135 | Cursor resultCursor = db.query( |
| 136 | TABLE_USB_SETTINGS, |
| 137 | null, |
| 138 | null, |
| 139 | null, |
| 140 | null, |
| 141 | null, |
| 142 | null)) { |
| 143 | return constructSettings(resultCursor); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | private List<UsbDeviceSettings> constructSettings(Cursor cursor) { |
| 148 | if (!cursor.isBeforeFirst()) { |
| 149 | throw new RuntimeException("Cursor is not reset to before first element"); |
| 150 | } |
| 151 | int serialNumberColumnId = cursor.getColumnIndex(COLUMN_SERIAL); |
| 152 | int vidColumnId = cursor.getColumnIndex(COLUMN_VID); |
| 153 | int pidColumnId = cursor.getColumnIndex(COLUMN_PID); |
| 154 | int deviceNameColumnId = cursor.getColumnIndex(COLUMN_NAME); |
| 155 | int handlerColumnId = cursor.getColumnIndex(COLUMN_HANDLER); |
| 156 | int aoapColumnId = cursor.getColumnIndex(COLUMN_AOAP); |
| 157 | List<UsbDeviceSettings> results = new ArrayList<>(cursor.getCount()); |
| 158 | while (cursor.moveToNext()) { |
| 159 | results.add(UsbDeviceSettings.constructSettings( |
| 160 | cursor.getString(serialNumberColumnId), |
| 161 | cursor.getInt(vidColumnId), |
| 162 | cursor.getInt(pidColumnId), |
| 163 | cursor.getString(deviceNameColumnId), |
| 164 | ComponentName.unflattenFromString( |
| 165 | cursor.getString(handlerColumnId)), |
| 166 | cursor.getInt(aoapColumnId) != 0)); |
| 167 | } |
| 168 | return results; |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Converts {@code UsbDeviceSettings} to {@code ContentValues}. |
| 173 | */ |
| 174 | public ContentValues settingsToContentValues(UsbDeviceSettings settings) { |
| 175 | ContentValues contentValues = new ContentValues(); |
| 176 | contentValues.put(COLUMN_SERIAL, settings.getSerialNumber()); |
| 177 | contentValues.put(COLUMN_VID, settings.getVid()); |
| 178 | contentValues.put(COLUMN_PID, settings.getPid()); |
| 179 | contentValues.put(COLUMN_NAME, settings.getDeviceName()); |
| 180 | contentValues.put(COLUMN_HANDLER, settings.getHandler().flattenToShortString()); |
| 181 | contentValues.put(COLUMN_AOAP, settings.getAoap() ? 1 : 0); |
| 182 | contentValues.put(COLUMN_DEFAULT_HANDLER, settings.isDefaultHandler() ? 1 : 0); |
| 183 | return contentValues; |
| 184 | } |
| 185 | |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 186 | private static class UsbSettingsDbHelper extends SQLiteOpenHelper { |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 187 | private static final int DATABASE_VERSION = 2; |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 188 | private static final String DATABASE_NAME = "usb_devices.db"; |
| 189 | |
tadvana | 63f42f4 | 2019-03-26 17:25:21 -0700 | [diff] [blame] | 190 | // we are using device protected storage because we may need to access the db before the |
| 191 | // user has authenticated |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 192 | UsbSettingsDbHelper(Context context) { |
tadvana | 63f42f4 | 2019-03-26 17:25:21 -0700 | [diff] [blame] | 193 | super( |
| 194 | context.createDeviceProtectedStorageContext(), |
| 195 | DATABASE_NAME, |
| 196 | null, |
| 197 | DATABASE_VERSION); |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 198 | } |
| 199 | |
| 200 | @Override |
| 201 | public void onCreate(SQLiteDatabase db) { |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 202 | createTable(db, TABLE_USB_SETTINGS); |
| 203 | createSerialIndex(db); |
| 204 | } |
| 205 | |
| 206 | private void createTable(SQLiteDatabase db, String tableName) { |
| 207 | db.execSQL("CREATE TABLE " + tableName + " (" |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 208 | + COLUMN_SERIAL + " TEXT," |
| 209 | + COLUMN_VID + " INTEGER," |
| 210 | + COLUMN_PID + " INTEGER," |
| 211 | + COLUMN_NAME + " TEXT, " |
| 212 | + COLUMN_HANDLER + " TEXT," |
| 213 | + COLUMN_AOAP + " INTEGER," |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 214 | + COLUMN_DEFAULT_HANDLER + " INTEGER," |
| 215 | + "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", " + COLUMN_PID |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 216 | + "))"); |
| 217 | } |
| 218 | |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 219 | private void createSerialIndex(SQLiteDatabase db) { |
| 220 | db.execSQL("CREATE INDEX " + TABLE_USB_SETTINGS + "_" + COLUMN_SERIAL + " ON " |
| 221 | + TABLE_USB_SETTINGS + "(" + COLUMN_SERIAL + ")"); |
| 222 | } |
| 223 | |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 224 | @Override |
| 225 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { |
Kevin Crossan | 4f208d8 | 2017-03-27 16:06:21 -0700 | [diff] [blame] | 226 | for (; oldVersion != newVersion; oldVersion++) { |
| 227 | switch (oldVersion) { |
| 228 | case 1: |
| 229 | String tempTableName = "temp_" + TABLE_USB_SETTINGS; |
| 230 | createTable(db, tempTableName); |
| 231 | db.execSQL("INSERT INTO " + tempTableName |
| 232 | + " SELECT * FROM " + TABLE_USB_SETTINGS); |
| 233 | db.execSQL("DROP TABLE " + TABLE_USB_SETTINGS); |
| 234 | db.execSQL("ALTER TABLE " + tempTableName + " RENAME TO " |
| 235 | + TABLE_USB_SETTINGS); |
| 236 | createSerialIndex(db); |
| 237 | break; |
| 238 | default: |
| 239 | throw new IllegalArgumentException( |
| 240 | "Unknown database version " + oldVersion); |
| 241 | } |
| 242 | } |
Kevin Crossan | d428549 | 2016-11-28 18:40:43 -0800 | [diff] [blame] | 243 | } |
| 244 | } |
| 245 | } |