1 /* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.android.bluetooth.opp; 34 35 import android.content.ContentProvider; 36 import android.content.ContentValues; 37 import android.content.Context; 38 import android.content.UriMatcher; 39 import android.database.Cursor; 40 import android.database.SQLException; 41 import android.database.sqlite.SQLiteDatabase; 42 import android.database.sqlite.SQLiteOpenHelper; 43 import android.database.sqlite.SQLiteQueryBuilder; 44 import android.net.Uri; 45 import android.util.Log; 46 47 /** 48 * This provider allows application to interact with Bluetooth OPP manager 49 */ 50 51 public final class BluetoothOppProvider extends ContentProvider { 52 53 private static final String TAG = "BluetoothOppProvider"; 54 private static final boolean D = Constants.DEBUG; 55 private static final boolean V = Constants.VERBOSE; 56 57 /** Database filename */ 58 private static final String DB_NAME = "btopp.db"; 59 60 /** Current database version */ 61 private static final int DB_VERSION = 1; 62 63 /** Database version from which upgrading is a nop */ 64 private static final int DB_VERSION_NOP_UPGRADE_FROM = 0; 65 66 /** Database version to which upgrading is a nop */ 67 private static final int DB_VERSION_NOP_UPGRADE_TO = 1; 68 69 /** Name of table in the database */ 70 private static final String DB_TABLE = "btopp"; 71 72 /** MIME type for the entire share list */ 73 private static final String SHARE_LIST_TYPE = "vnd.android.cursor.dir/vnd.android.btopp"; 74 75 /** MIME type for an individual share */ 76 private static final String SHARE_TYPE = "vnd.android.cursor.item/vnd.android.btopp"; 77 78 /** URI matcher used to recognize URIs sent by applications */ 79 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 80 81 /** URI matcher constant for the URI of the entire share list */ 82 private static final int SHARES = 1; 83 84 /** URI matcher constant for the URI of an individual share */ 85 private static final int SHARES_ID = 2; 86 87 static { 88 sURIMatcher.addURI("com.android.bluetooth.opp", "btopp", SHARES); 89 sURIMatcher.addURI("com.android.bluetooth.opp", "btopp/#", SHARES_ID); 90 } 91 92 /** The database that lies underneath this content provider */ 93 private SQLiteOpenHelper mOpenHelper = null; 94 95 /** 96 * Creates and updated database on demand when opening it. Helper class to 97 * create database the first time the provider is initialized and upgrade it 98 * when a new version of the provider needs an updated version of the 99 * database. 100 */ 101 private final class DatabaseHelper extends SQLiteOpenHelper { 102 DatabaseHelper(final Context context)103 DatabaseHelper(final Context context) { 104 super(context, DB_NAME, null, DB_VERSION); 105 } 106 107 /** 108 * Creates database the first time we try to open it. 109 */ 110 @Override onCreate(final SQLiteDatabase db)111 public void onCreate(final SQLiteDatabase db) { 112 if (V) { 113 Log.v(TAG, "populating new database"); 114 } 115 createTable(db); 116 } 117 118 /** 119 * Updates the database format when a content provider is used with a 120 * database that was created with a different format. 121 */ 122 @Override onUpgrade(final SQLiteDatabase db, int oldV, final int newV)123 public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) { 124 if (oldV == DB_VERSION_NOP_UPGRADE_FROM) { 125 if (newV == DB_VERSION_NOP_UPGRADE_TO) { 126 return; 127 } 128 // NOP_FROM and NOP_TO are identical, just in different code lines. 129 // Upgrading from NOP_FROM is the same as upgrading from NOP_TO. 130 oldV = DB_VERSION_NOP_UPGRADE_TO; 131 } 132 Log.i(TAG, "Upgrading downloads database from version " + oldV + " to " + newV 133 + ", which will destroy all old data"); 134 dropTable(db); 135 createTable(db); 136 } 137 138 } 139 createTable(SQLiteDatabase db)140 private void createTable(SQLiteDatabase db) { 141 try { 142 db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID 143 + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, " 144 + BluetoothShare.FILENAME_HINT + " TEXT, " + BluetoothShare._DATA + " TEXT, " 145 + BluetoothShare.MIMETYPE + " TEXT, " + BluetoothShare.DIRECTION + " INTEGER, " 146 + BluetoothShare.DESTINATION + " TEXT, " + BluetoothShare.VISIBILITY 147 + " INTEGER, " + BluetoothShare.USER_CONFIRMATION + " INTEGER, " 148 + BluetoothShare.STATUS + " INTEGER, " + BluetoothShare.TOTAL_BYTES 149 + " INTEGER, " + BluetoothShare.CURRENT_BYTES + " INTEGER, " 150 + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED 151 + " INTEGER); "); 152 } catch (SQLException ex) { 153 Log.e(TAG, "createTable: Failed."); 154 throw ex; 155 } 156 } 157 dropTable(SQLiteDatabase db)158 private void dropTable(SQLiteDatabase db) { 159 try { 160 db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); 161 } catch (SQLException ex) { 162 Log.e(TAG, "dropTable: Failed."); 163 throw ex; 164 } 165 } 166 167 @Override getType(Uri uri)168 public String getType(Uri uri) { 169 int match = sURIMatcher.match(uri); 170 switch (match) { 171 case SHARES: 172 return SHARE_LIST_TYPE; 173 case SHARES_ID: 174 return SHARE_TYPE; 175 default: 176 throw new IllegalArgumentException("Unknown URI in getType(): " + uri); 177 } 178 } 179 copyString(String key, ContentValues from, ContentValues to)180 private static void copyString(String key, ContentValues from, ContentValues to) { 181 String s = from.getAsString(key); 182 if (s != null) { 183 to.put(key, s); 184 } 185 } 186 copyInteger(String key, ContentValues from, ContentValues to)187 private static void copyInteger(String key, ContentValues from, ContentValues to) { 188 Integer i = from.getAsInteger(key); 189 if (i != null) { 190 to.put(key, i); 191 } 192 } 193 copyLong(String key, ContentValues from, ContentValues to)194 private static void copyLong(String key, ContentValues from, ContentValues to) { 195 Long i = from.getAsLong(key); 196 if (i != null) { 197 to.put(key, i); 198 } 199 } 200 201 @Override insert(Uri uri, ContentValues values)202 public Uri insert(Uri uri, ContentValues values) { 203 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 204 205 if (sURIMatcher.match(uri) != SHARES) { 206 throw new IllegalArgumentException("insert: Unknown/Invalid URI " + uri); 207 } 208 209 ContentValues filteredValues = new ContentValues(); 210 211 copyString(BluetoothShare.URI, values, filteredValues); 212 copyString(BluetoothShare.FILENAME_HINT, values, filteredValues); 213 copyString(BluetoothShare.MIMETYPE, values, filteredValues); 214 copyString(BluetoothShare.DESTINATION, values, filteredValues); 215 216 copyInteger(BluetoothShare.VISIBILITY, values, filteredValues); 217 copyLong(BluetoothShare.TOTAL_BYTES, values, filteredValues); 218 if (values.getAsInteger(BluetoothShare.VISIBILITY) == null) { 219 filteredValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_VISIBLE); 220 } 221 Integer dir = values.getAsInteger(BluetoothShare.DIRECTION); 222 Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION); 223 224 if (dir == null) { 225 dir = BluetoothShare.DIRECTION_OUTBOUND; 226 } 227 if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) { 228 con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED; 229 } 230 if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) { 231 con = BluetoothShare.USER_CONFIRMATION_PENDING; 232 } 233 filteredValues.put(BluetoothShare.USER_CONFIRMATION, con); 234 filteredValues.put(BluetoothShare.DIRECTION, dir); 235 236 filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING); 237 filteredValues.put(Constants.MEDIA_SCANNED, 0); 238 239 Long ts = values.getAsLong(BluetoothShare.TIMESTAMP); 240 if (ts == null) { 241 ts = System.currentTimeMillis(); 242 } 243 filteredValues.put(BluetoothShare.TIMESTAMP, ts); 244 245 Context context = getContext(); 246 247 long rowID = db.insert(DB_TABLE, null, filteredValues); 248 249 if (rowID == -1) { 250 Log.w(TAG, "couldn't insert " + uri + "into btopp database"); 251 return null; 252 } 253 254 context.getContentResolver().notifyChange(uri, null); 255 256 return Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID); 257 } 258 259 @Override onCreate()260 public boolean onCreate() { 261 mOpenHelper = new DatabaseHelper(getContext()); 262 return true; 263 } 264 265 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)266 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 267 String sortOrder) { 268 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 269 270 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 271 qb.setStrict(true); 272 273 int match = sURIMatcher.match(uri); 274 switch (match) { 275 case SHARES: 276 qb.setTables(DB_TABLE); 277 break; 278 case SHARES_ID: 279 qb.setTables(DB_TABLE); 280 qb.appendWhere(BluetoothShare._ID + "="); 281 qb.appendWhere(uri.getPathSegments().get(1)); 282 break; 283 default: 284 throw new IllegalArgumentException("Unknown URI: " + uri); 285 } 286 287 if (V) { 288 java.lang.StringBuilder sb = new java.lang.StringBuilder(); 289 sb.append("starting query, database is "); 290 if (db != null) { 291 sb.append("not "); 292 } 293 sb.append("null; "); 294 if (projection == null) { 295 sb.append("projection is null; "); 296 } else if (projection.length == 0) { 297 sb.append("projection is empty; "); 298 } else { 299 for (int i = 0; i < projection.length; ++i) { 300 sb.append("projection["); 301 sb.append(i); 302 sb.append("] is "); 303 sb.append(projection[i]); 304 sb.append("; "); 305 } 306 } 307 sb.append("selection is "); 308 sb.append(selection); 309 sb.append("; "); 310 if (selectionArgs == null) { 311 sb.append("selectionArgs is null; "); 312 } else if (selectionArgs.length == 0) { 313 sb.append("selectionArgs is empty; "); 314 } else { 315 for (int i = 0; i < selectionArgs.length; ++i) { 316 sb.append("selectionArgs["); 317 sb.append(i); 318 sb.append("] is "); 319 sb.append(selectionArgs[i]); 320 sb.append("; "); 321 } 322 } 323 sb.append("sort is "); 324 sb.append(sortOrder); 325 sb.append("."); 326 Log.v(TAG, sb.toString()); 327 } 328 329 Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); 330 331 if (ret == null) { 332 Log.w(TAG, "query failed in downloads database"); 333 return null; 334 } 335 336 ret.setNotificationUri(getContext().getContentResolver(), uri); 337 return ret; 338 } 339 340 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)341 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 342 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 343 344 int count = 0; 345 long rowId; 346 347 int match = sURIMatcher.match(uri); 348 switch (match) { 349 case SHARES: 350 case SHARES_ID: { 351 String myWhere; 352 if (selection != null) { 353 if (match == SHARES) { 354 myWhere = "( " + selection + " )"; 355 } else { 356 myWhere = "( " + selection + " ) AND "; 357 } 358 } else { 359 myWhere = ""; 360 } 361 if (match == SHARES_ID) { 362 String segment = uri.getPathSegments().get(1); 363 rowId = Long.parseLong(segment); 364 myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; 365 } 366 367 if (values.size() > 0) { 368 count = db.update(DB_TABLE, values, myWhere, selectionArgs); 369 } 370 break; 371 } 372 default: 373 throw new UnsupportedOperationException("Cannot update unknown URI: " + uri); 374 } 375 getContext().getContentResolver().notifyChange(uri, null); 376 377 return count; 378 } 379 380 @Override delete(Uri uri, String selection, String[] selectionArgs)381 public int delete(Uri uri, String selection, String[] selectionArgs) { 382 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 383 int count; 384 int match = sURIMatcher.match(uri); 385 switch (match) { 386 case SHARES: 387 case SHARES_ID: { 388 String myWhere; 389 if (selection != null) { 390 if (match == SHARES) { 391 myWhere = "( " + selection + " )"; 392 } else { 393 myWhere = "( " + selection + " ) AND "; 394 } 395 } else { 396 myWhere = ""; 397 } 398 if (match == SHARES_ID) { 399 String segment = uri.getPathSegments().get(1); 400 long rowId = Long.parseLong(segment); 401 myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; 402 } 403 404 count = db.delete(DB_TABLE, myWhere, selectionArgs); 405 break; 406 } 407 default: 408 throw new UnsupportedOperationException("Cannot delete unknown URI: " + uri); 409 } 410 getContext().getContentResolver().notifyChange(uri, null); 411 return count; 412 } 413 } 414