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