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 (AoapInterface.isDeviceInAoapMode(device)) { 57 selection = COLUMN_SERIAL + " = ? AND " + COLUMN_AOAP + " = 1"; 58 selectionArgs.add(serial); 59 } else if (serial == null) { 60 selection = COLUMN_SERIAL + " IS NULL"; 61 } else { 62 selection = COLUMN_SERIAL + " = ?"; 63 selectionArgs.add(serial); 64 } 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); 72 } 73 74 /** 75 * Returns settings for {@serialNumber} or null if it doesn't exist. 76 */ 77 @Nullable getSettings(UsbDevice device)78 public UsbDeviceSettings getSettings(UsbDevice device) { 79 try (SQLiteDatabase db = mDbHelper.getReadableDatabase(); 80 Cursor resultCursor = queryFor(db, device)) { 81 if (resultCursor.getCount() > 1) { 82 throw new RuntimeException("Querying for device: " + device 83 + " returned " + resultCursor.getCount() + " results"); 84 } 85 if (resultCursor.getCount() == 0) { 86 Log.w(TAG, "Usb setting missing for device: " + device); 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 */ saveSettings(UsbDeviceSettings settings)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 */ deleteSettings(String serialNumber, int vid, int pid)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 */ getAllSettings()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 constructSettings(Cursor cursor)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 */ settingsToContentValues(UsbDeviceSettings settings)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 186 private static class UsbSettingsDbHelper extends SQLiteOpenHelper { 187 private static final int DATABASE_VERSION = 2; 188 private static final String DATABASE_NAME = "usb_devices.db"; 189 190 // we are using device protected storage because we may need to access the db before the 191 // user has authenticated UsbSettingsDbHelper(Context context)192 UsbSettingsDbHelper(Context context) { 193 super( 194 context.createDeviceProtectedStorageContext(), 195 DATABASE_NAME, 196 null, 197 DATABASE_VERSION); 198 } 199 200 @Override onCreate(SQLiteDatabase db)201 public void onCreate(SQLiteDatabase db) { 202 createTable(db, TABLE_USB_SETTINGS); 203 createSerialIndex(db); 204 } 205 createTable(SQLiteDatabase db, String tableName)206 private void createTable(SQLiteDatabase db, String tableName) { 207 db.execSQL("CREATE TABLE " + tableName + " (" 208 + COLUMN_SERIAL + " TEXT," 209 + COLUMN_VID + " INTEGER," 210 + COLUMN_PID + " INTEGER," 211 + COLUMN_NAME + " TEXT, " 212 + COLUMN_HANDLER + " TEXT," 213 + COLUMN_AOAP + " INTEGER," 214 + COLUMN_DEFAULT_HANDLER + " INTEGER," 215 + "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", " + COLUMN_PID 216 + "))"); 217 } 218 createSerialIndex(SQLiteDatabase db)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 224 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)225 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 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 } 243 } 244 } 245 } 246