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.util.Log;
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /**
30  * Provides API to persist USB device settings.
31  */
32 public final class UsbSettingsStorage {
33     private static final String TAG = UsbSettingsStorage.class.getSimpleName();
34 
35     private static final String TABLE_USB_SETTINGS = "usb_devices";
36     private static final String COLUMN_SERIAL = "serial";
37     private static final String COLUMN_VID = "vid";
38     private static final String COLUMN_PID = "pid";
39     private static final String COLUMN_NAME = "name";
40     private static final String COLUMN_HANDLER = "handler";
41     private static final String COLUMN_AOAP = "aoap";
42     private static final String COLUMN_DEFAULT_HANDLER = "default_handler";
43 
44     private final UsbSettingsDbHelper mDbHelper;
45 
UsbSettingsStorage(Context context)46     public UsbSettingsStorage(Context context) {
47         mDbHelper = new UsbSettingsDbHelper(context);
48     }
49 
50     /**
51      * Returns settings for {@serialNumber} or null if it doesn't exist.
52      */
53     @Nullable
getSettings(String serialNumber)54     public UsbDeviceSettings getSettings(String serialNumber) {
55         try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
56              Cursor resultCursor = db.query(
57                      TABLE_USB_SETTINGS,
58                      null,
59                      COLUMN_SERIAL + " = ?",
60                      new String[]{serialNumber},
61                      null,
62                      null,
63                      null)) {
64             if (resultCursor.getCount() > 1) {
65                 throw new RuntimeException("Querying for serial number: " + serialNumber
66                         + " returned " + resultCursor.getCount() + " results");
67             }
68             if (resultCursor.getCount() == 0) {
69                 Log.w(TAG, "Usb setting missing for device serial: " + serialNumber);
70                 return null;
71             }
72             List<UsbDeviceSettings> settings = constructSettings(resultCursor);
73             return settings.get(0);
74         }
75     }
76 
77     /**
78      * Saves or updates settings for USB device.
79      */
saveSettings(UsbDeviceSettings settings)80     public void saveSettings(UsbDeviceSettings settings) {
81         try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
82             long result = db.replace(
83                     TABLE_USB_SETTINGS,
84                     null,
85                     settingsToContentValues(settings));
86             if (result == -1) {
87                 Log.e(TAG, "Failed to save settings: " + settings);
88             }
89         }
90     }
91 
92     /**
93      * Delete settings for USB device.
94      */
deleteSettings(String serialNumber, int vid, int pid)95     public void deleteSettings(String serialNumber, int vid, int pid) {
96         try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
97             int result = db.delete(
98                     TABLE_USB_SETTINGS,
99                     COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID
100                     + " = ?",
101                     new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)});
102             if (result == 0) {
103                 Log.w(TAG, "No settings with serialNumber: " + serialNumber
104                         + " vid: " + vid + " pid: " + pid);
105             }
106             if (result > 1) {
107                 Log.e(TAG, "Deleted multiple rows (" + result + ") for serialNumber: "
108                         + serialNumber + " vid: " + vid + " pid: " + pid);
109             }
110         }
111     }
112 
113     /**
114      * Returns all saved settings.
115      */
getAllSettings()116     public List<UsbDeviceSettings> getAllSettings() {
117         try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
118              Cursor resultCursor = db.query(
119                      TABLE_USB_SETTINGS,
120                      null,
121                      null,
122                      null,
123                      null,
124                      null,
125                      null)) {
126             return constructSettings(resultCursor);
127         }
128     }
129 
constructSettings(Cursor cursor)130     private List<UsbDeviceSettings> constructSettings(Cursor cursor) {
131         if (!cursor.isBeforeFirst()) {
132             throw new RuntimeException("Cursor is not reset to before first element");
133         }
134         int serialNumberColumnId = cursor.getColumnIndex(COLUMN_SERIAL);
135         int vidColumnId = cursor.getColumnIndex(COLUMN_VID);
136         int pidColumnId = cursor.getColumnIndex(COLUMN_PID);
137         int deviceNameColumnId = cursor.getColumnIndex(COLUMN_NAME);
138         int handlerColumnId = cursor.getColumnIndex(COLUMN_HANDLER);
139         int aoapColumnId = cursor.getColumnIndex(COLUMN_AOAP);
140         List<UsbDeviceSettings> results = new ArrayList<>(cursor.getCount());
141         while (cursor.moveToNext()) {
142             results.add(UsbDeviceSettings.constructSettings(
143                                 cursor.getString(serialNumberColumnId),
144                                 cursor.getInt(vidColumnId),
145                                 cursor.getInt(pidColumnId),
146                                 cursor.getString(deviceNameColumnId),
147                                 ComponentName.unflattenFromString(
148                                         cursor.getString(handlerColumnId)),
149                                 cursor.getInt(aoapColumnId) != 0));
150         }
151         return results;
152     }
153 
154     /**
155      * Converts {@code UsbDeviceSettings} to {@code ContentValues}.
156      */
settingsToContentValues(UsbDeviceSettings settings)157     public ContentValues settingsToContentValues(UsbDeviceSettings settings) {
158         ContentValues contentValues = new ContentValues();
159         contentValues.put(COLUMN_SERIAL, settings.getSerialNumber());
160         contentValues.put(COLUMN_VID, settings.getVid());
161         contentValues.put(COLUMN_PID, settings.getPid());
162         contentValues.put(COLUMN_NAME, settings.getDeviceName());
163         contentValues.put(COLUMN_HANDLER, settings.getHandler().flattenToShortString());
164         contentValues.put(COLUMN_AOAP, settings.getAoap() ? 1 : 0);
165         contentValues.put(COLUMN_DEFAULT_HANDLER, settings.isDefaultHandler() ? 1 : 0);
166         return contentValues;
167     }
168 
169 
170     private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
171         private static final int DATABASE_VERSION = 1;
172         private static final String DATABASE_NAME = "usb_devices.db";
173 
UsbSettingsDbHelper(Context context)174         UsbSettingsDbHelper(Context context) {
175             super(context, DATABASE_NAME, null, DATABASE_VERSION);
176         }
177 
178         @Override
onCreate(SQLiteDatabase db)179         public void onCreate(SQLiteDatabase db) {
180             db.execSQL("CREATE TABLE " + TABLE_USB_SETTINGS + " ("
181                     + COLUMN_SERIAL + " TEXT,"
182                     + COLUMN_VID + " INTEGER,"
183                     + COLUMN_PID + " INTEGER,"
184                     + COLUMN_NAME + " TEXT, "
185                     + COLUMN_HANDLER + " TEXT,"
186                     + COLUMN_AOAP + " INTEGER,"
187                     + COLUMN_DEFAULT_HANDLER + " INTEGER," + "PRIMARY KEY (" + COLUMN_SERIAL
188                     + "))");
189         }
190 
191         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)192         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
193             // Do nothing at this point. Not required for v1 database.
194         }
195     }
196 }
197