blob: f9ef3ebb56994606d85ba1955c76b183250a241d [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.car.usb.handler;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.hardware.usb.UsbDevice;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Provides API to persist USB device settings.
*/
public final class UsbSettingsStorage {
private static final String TAG = UsbSettingsStorage.class.getSimpleName();
private static final String TABLE_USB_SETTINGS = "usb_devices";
private static final String COLUMN_SERIAL = "serial";
private static final String COLUMN_VID = "vid";
private static final String COLUMN_PID = "pid";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_HANDLER = "handler";
private static final String COLUMN_AOAP = "aoap";
private static final String COLUMN_DEFAULT_HANDLER = "default_handler";
private final UsbSettingsDbHelper mDbHelper;
public UsbSettingsStorage(Context context) {
mDbHelper = new UsbSettingsDbHelper(context);
}
private Cursor queryFor(SQLiteDatabase db, UsbDevice device) {
String serial = device.getSerialNumber();
String selection;
List<String> selectionArgs = new ArrayList<>();
if (AoapInterface.isDeviceInAoapMode(device)) {
selection = COLUMN_SERIAL + " = ? AND " + COLUMN_AOAP + " = 1";
selectionArgs.add(serial);
} else if (serial == null) {
selection = COLUMN_SERIAL + " IS NULL";
} else {
selection = COLUMN_SERIAL + " = ?";
selectionArgs.add(serial);
}
selection += " AND " + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?";
selectionArgs.add(String.valueOf(device.getVendorId()));
selectionArgs.add(String.valueOf(device.getProductId()));
return db.query(TABLE_USB_SETTINGS, null, selection,
selectionArgs.toArray(new String[0]), null, null, null);
}
/**
* Returns settings for {@serialNumber} or null if it doesn't exist.
*/
@Nullable
public UsbDeviceSettings getSettings(UsbDevice device) {
try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor resultCursor = queryFor(db, device)) {
if (resultCursor.getCount() > 1) {
throw new RuntimeException("Querying for device: " + device
+ " returned " + resultCursor.getCount() + " results");
}
if (resultCursor.getCount() == 0) {
Log.w(TAG, "Usb setting missing for device: " + device);
return null;
}
List<UsbDeviceSettings> settings = constructSettings(resultCursor);
return settings.get(0);
}
}
/**
* Saves or updates settings for USB device.
*/
public void saveSettings(UsbDeviceSettings settings) {
try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
long result = db.replace(
TABLE_USB_SETTINGS,
null,
settingsToContentValues(settings));
if (result == -1) {
Log.e(TAG, "Failed to save settings: " + settings);
}
}
}
/**
* Delete settings for USB device.
*/
public void deleteSettings(String serialNumber, int vid, int pid) {
try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
int result = db.delete(
TABLE_USB_SETTINGS,
COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID
+ " = ?",
new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)});
if (result == 0) {
Log.w(TAG, "No settings with serialNumber: " + serialNumber
+ " vid: " + vid + " pid: " + pid);
}
if (result > 1) {
Log.e(TAG, "Deleted multiple rows (" + result + ") for serialNumber: "
+ serialNumber + " vid: " + vid + " pid: " + pid);
}
}
}
/**
* Returns all saved settings.
*/
public List<UsbDeviceSettings> getAllSettings() {
try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor resultCursor = db.query(
TABLE_USB_SETTINGS,
null,
null,
null,
null,
null,
null)) {
return constructSettings(resultCursor);
}
}
private List<UsbDeviceSettings> constructSettings(Cursor cursor) {
if (!cursor.isBeforeFirst()) {
throw new RuntimeException("Cursor is not reset to before first element");
}
int serialNumberColumnId = cursor.getColumnIndex(COLUMN_SERIAL);
int vidColumnId = cursor.getColumnIndex(COLUMN_VID);
int pidColumnId = cursor.getColumnIndex(COLUMN_PID);
int deviceNameColumnId = cursor.getColumnIndex(COLUMN_NAME);
int handlerColumnId = cursor.getColumnIndex(COLUMN_HANDLER);
int aoapColumnId = cursor.getColumnIndex(COLUMN_AOAP);
List<UsbDeviceSettings> results = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
results.add(UsbDeviceSettings.constructSettings(
cursor.getString(serialNumberColumnId),
cursor.getInt(vidColumnId),
cursor.getInt(pidColumnId),
cursor.getString(deviceNameColumnId),
ComponentName.unflattenFromString(
cursor.getString(handlerColumnId)),
cursor.getInt(aoapColumnId) != 0));
}
return results;
}
/**
* Converts {@code UsbDeviceSettings} to {@code ContentValues}.
*/
public ContentValues settingsToContentValues(UsbDeviceSettings settings) {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_SERIAL, settings.getSerialNumber());
contentValues.put(COLUMN_VID, settings.getVid());
contentValues.put(COLUMN_PID, settings.getPid());
contentValues.put(COLUMN_NAME, settings.getDeviceName());
contentValues.put(COLUMN_HANDLER, settings.getHandler().flattenToShortString());
contentValues.put(COLUMN_AOAP, settings.isAaop() ? 1 : 0);
contentValues.put(COLUMN_DEFAULT_HANDLER, settings.isDefaultHandler() ? 1 : 0);
return contentValues;
}
private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 2;
private static final String DATABASE_NAME = "usb_devices.db";
// we are using device protected storage because we may need to access the db before the
// user has authenticated
UsbSettingsDbHelper(Context context) {
super(
context.createDeviceProtectedStorageContext(),
DATABASE_NAME,
null,
DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db, TABLE_USB_SETTINGS);
createSerialIndex(db);
}
private void createTable(SQLiteDatabase db, String tableName) {
db.execSQL("CREATE TABLE " + tableName + " ("
+ COLUMN_SERIAL + " TEXT,"
+ COLUMN_VID + " INTEGER,"
+ COLUMN_PID + " INTEGER,"
+ COLUMN_NAME + " TEXT, "
+ COLUMN_HANDLER + " TEXT,"
+ COLUMN_AOAP + " INTEGER,"
+ COLUMN_DEFAULT_HANDLER + " INTEGER,"
+ "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", " + COLUMN_PID
+ "))");
}
private void createSerialIndex(SQLiteDatabase db) {
db.execSQL("CREATE INDEX " + TABLE_USB_SETTINGS + "_" + COLUMN_SERIAL + " ON "
+ TABLE_USB_SETTINGS + "(" + COLUMN_SERIAL + ")");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
int version = oldVersion;
for (; version != newVersion; version++) {
switch (version) {
case 1:
String tempTableName = "temp_" + TABLE_USB_SETTINGS;
createTable(db, tempTableName);
db.execSQL("INSERT INTO " + tempTableName
+ " SELECT * FROM " + TABLE_USB_SETTINGS);
db.execSQL("DROP TABLE " + TABLE_USB_SETTINGS);
db.execSQL("ALTER TABLE " + tempTableName + " RENAME TO "
+ TABLE_USB_SETTINGS);
createSerialIndex(db);
break;
default:
throw new IllegalArgumentException(
"Unknown database version " + oldVersion);
}
}
}
}
}