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.Intent; 39 import android.database.Cursor; 40 import android.database.SQLException; 41 import android.content.UriMatcher; 42 import android.database.sqlite.SQLiteDatabase; 43 import android.database.sqlite.SQLiteOpenHelper; 44 import android.database.sqlite.SQLiteQueryBuilder; 45 import android.net.Uri; 46 import android.util.Log; 47 48 /** 49 * This provider allows application to interact with Bluetooth OPP manager 50 */ 51 52 public final class BluetoothOppProvider extends ContentProvider { 53 54 private static final String TAG = "BluetoothOppProvider"; 55 private static final boolean D = Constants.DEBUG; 56 private static final boolean V = Constants.VERBOSE; 57 58 /** Database filename */ 59 private static final String DB_NAME = "btopp.db"; 60 61 /** Current database version */ 62 private static final int DB_VERSION = 1; 63 64 /** Database version from which upgrading is a nop */ 65 private static final int DB_VERSION_NOP_UPGRADE_FROM = 0; 66 67 /** Database version to which upgrading is a nop */ 68 private static final int DB_VERSION_NOP_UPGRADE_TO = 1; 69 70 /** Name of table in the database */ 71 private static final String DB_TABLE = "btopp"; 72 73 /** MIME type for the entire share list */ 74 private static final String SHARE_LIST_TYPE = "vnd.android.cursor.dir/vnd.android.btopp"; 75 76 /** MIME type for an individual share */ 77 private static final String SHARE_TYPE = "vnd.android.cursor.item/vnd.android.btopp"; 78 79 /** URI matcher used to recognize URIs sent by applications */ 80 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 81 82 /** URI matcher constant for the URI of the entire share list */ 83 private static final int SHARES = 1; 84 85 /** URI matcher constant for the URI of an individual share */ 86 private static final int SHARES_ID = 2; 87 88 static { 89 sURIMatcher.addURI("com.android.bluetooth.opp", "btopp", SHARES); 90 sURIMatcher.addURI("com.android.bluetooth.opp", "btopp/#", SHARES_ID); 91 } 92 93 /** The database that lies underneath this content provider */ 94 private SQLiteOpenHelper mOpenHelper = null; 95 96 /** 97 * Creates and updated database on demand when opening it. Helper class to 98 * create database the first time the provider is initialized and upgrade it 99 * when a new version of the provider needs an updated version of the 100 * database. 101 */ 102 private final class DatabaseHelper extends SQLiteOpenHelper { 103 DatabaseHelper(final Context context)104 public DatabaseHelper(final Context context) { 105 super(context, DB_NAME, null, DB_VERSION); 106 } 107 108 /** 109 * Creates database the first time we try to open it. 110 */ 111 @Override onCreate(final SQLiteDatabase db)112 public void onCreate(final SQLiteDatabase db) { 113 if (V) Log.v(TAG, "populating new database"); 114 createTable(db); 115 } 116 117 //TODO: use this function to check garbage transfer left in db, for example, 118 // a crash incoming file 119 /* 120 * (not a javadoc comment) Checks data integrity when opening the 121 * database. 122 */ 123 /* 124 * @Override public void onOpen(final SQLiteDatabase db) { 125 * super.onOpen(db); } 126 */ 127 128 /** 129 * Updates the database format when a content provider is used with a 130 * database that was created with a different format. 131 */ 132 // Note: technically, this could also be a downgrade, so if we want 133 // to gracefully handle upgrades we should be careful about 134 // what to do on downgrades. 135 @Override onUpgrade(final SQLiteDatabase db, int oldV, final int newV)136 public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) { 137 if (oldV == DB_VERSION_NOP_UPGRADE_FROM) { 138 if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op 139 // upgrade. 140 return; 141 } 142 // NOP_FROM and NOP_TO are identical, just in different 143 // codelines. Upgrading 144 // from NOP_FROM is the same as upgrading from NOP_TO. 145 oldV = DB_VERSION_NOP_UPGRADE_TO; 146 } 147 Log.i(TAG, "Upgrading downloads database from version " + oldV + " to " 148 + newV + ", which will destroy all old data"); 149 dropTable(db); 150 createTable(db); 151 } 152 153 } 154 createTable(SQLiteDatabase db)155 private void createTable(SQLiteDatabase db) { 156 try { 157 db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID 158 + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, " 159 + BluetoothShare.FILENAME_HINT + " TEXT, " + BluetoothShare._DATA + " TEXT, " 160 + BluetoothShare.MIMETYPE + " TEXT, " + BluetoothShare.DIRECTION + " INTEGER, " 161 + BluetoothShare.DESTINATION + " TEXT, " + BluetoothShare.VISIBILITY 162 + " INTEGER, " + BluetoothShare.USER_CONFIRMATION + " INTEGER, " 163 + BluetoothShare.STATUS + " INTEGER, " + BluetoothShare.TOTAL_BYTES 164 + " INTEGER, " + BluetoothShare.CURRENT_BYTES + " INTEGER, " 165 + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED 166 + " INTEGER); "); 167 } catch (SQLException ex) { 168 Log.e(TAG, "couldn't create table in downloads database"); 169 throw ex; 170 } 171 } 172 dropTable(SQLiteDatabase db)173 private void dropTable(SQLiteDatabase db) { 174 try { 175 db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); 176 } catch (SQLException ex) { 177 Log.e(TAG, "couldn't drop table in downloads database"); 178 throw ex; 179 } 180 } 181 182 @Override getType(Uri uri)183 public String getType(Uri uri) { 184 int match = sURIMatcher.match(uri); 185 switch (match) { 186 case SHARES: { 187 return SHARE_LIST_TYPE; 188 } 189 case SHARES_ID: { 190 return SHARE_TYPE; 191 } 192 default: { 193 if (D) Log.d(TAG, "calling getType on an unknown URI: " + uri); 194 throw new IllegalArgumentException("Unknown URI: " + uri); 195 } 196 } 197 } 198 copyString(String key, ContentValues from, ContentValues to)199 private static final void copyString(String key, ContentValues from, ContentValues to) { 200 String s = from.getAsString(key); 201 if (s != null) { 202 to.put(key, s); 203 } 204 } 205 copyInteger(String key, ContentValues from, ContentValues to)206 private static final void copyInteger(String key, ContentValues from, ContentValues to) { 207 Integer i = from.getAsInteger(key); 208 if (i != null) { 209 to.put(key, i); 210 } 211 } 212 copyLong(String key, ContentValues from, ContentValues to)213 private static final void copyLong(String key, ContentValues from, ContentValues to) { 214 Long i = from.getAsLong(key); 215 if (i != null) { 216 to.put(key, i); 217 } 218 } 219 220 @Override insert(Uri uri, ContentValues values)221 public Uri insert(Uri uri, ContentValues values) { 222 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 223 224 if (sURIMatcher.match(uri) != SHARES) { 225 if (D) Log.d(TAG, "calling insert on an unknown/invalid URI: " + uri); 226 throw new IllegalArgumentException("Unknown/Invalid URI " + uri); 227 } 228 229 ContentValues filteredValues = new ContentValues(); 230 231 copyString(BluetoothShare.URI, values, filteredValues); 232 copyString(BluetoothShare.FILENAME_HINT, values, filteredValues); 233 copyString(BluetoothShare.MIMETYPE, values, filteredValues); 234 copyString(BluetoothShare.DESTINATION, values, filteredValues); 235 236 copyInteger(BluetoothShare.VISIBILITY, values, filteredValues); 237 copyLong(BluetoothShare.TOTAL_BYTES, values, filteredValues); 238 if (values.getAsInteger(BluetoothShare.VISIBILITY) == null) { 239 filteredValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_VISIBLE); 240 } 241 Integer dir = values.getAsInteger(BluetoothShare.DIRECTION); 242 Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION); 243 244 if (values.getAsInteger(BluetoothShare.DIRECTION) == null) { 245 dir = BluetoothShare.DIRECTION_OUTBOUND; 246 } 247 if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) { 248 con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED; 249 } 250 if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) { 251 con = BluetoothShare.USER_CONFIRMATION_PENDING; 252 } 253 filteredValues.put(BluetoothShare.USER_CONFIRMATION, con); 254 filteredValues.put(BluetoothShare.DIRECTION, dir); 255 256 filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING); 257 filteredValues.put(Constants.MEDIA_SCANNED, 0); 258 259 Long ts = values.getAsLong(BluetoothShare.TIMESTAMP); 260 if (ts == null) { 261 ts = System.currentTimeMillis(); 262 } 263 filteredValues.put(BluetoothShare.TIMESTAMP, ts); 264 265 Context context = getContext(); 266 context.startService(new Intent(context, BluetoothOppService.class)); 267 268 long rowID = db.insert(DB_TABLE, null, filteredValues); 269 270 Uri ret = null; 271 272 if (rowID != -1) { 273 context.startService(new Intent(context, BluetoothOppService.class)); 274 ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID); 275 context.getContentResolver().notifyChange(uri, null); 276 } else { 277 if (D) Log.d(TAG, "couldn't insert into btopp database"); 278 } 279 280 return ret; 281 } 282 283 @Override onCreate()284 public boolean onCreate() { 285 mOpenHelper = new DatabaseHelper(getContext()); 286 return true; 287 } 288 289 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)290 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 291 String sortOrder) { 292 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 293 294 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 295 qb.setStrict(true); 296 297 int match = sURIMatcher.match(uri); 298 switch (match) { 299 case SHARES: { 300 qb.setTables(DB_TABLE); 301 break; 302 } 303 case SHARES_ID: { 304 qb.setTables(DB_TABLE); 305 qb.appendWhere(BluetoothShare._ID + "="); 306 qb.appendWhere(uri.getPathSegments().get(1)); 307 break; 308 } 309 default: { 310 if (D) Log.d(TAG, "querying unknown URI: " + uri); 311 throw new IllegalArgumentException("Unknown URI: " + uri); 312 } 313 } 314 315 if (V) { 316 java.lang.StringBuilder sb = new java.lang.StringBuilder(); 317 sb.append("starting query, database is "); 318 if (db != null) { 319 sb.append("not "); 320 } 321 sb.append("null; "); 322 if (projection == null) { 323 sb.append("projection is null; "); 324 } else if (projection.length == 0) { 325 sb.append("projection is empty; "); 326 } else { 327 for (int i = 0; i < projection.length; ++i) { 328 sb.append("projection["); 329 sb.append(i); 330 sb.append("] is "); 331 sb.append(projection[i]); 332 sb.append("; "); 333 } 334 } 335 sb.append("selection is "); 336 sb.append(selection); 337 sb.append("; "); 338 if (selectionArgs == null) { 339 sb.append("selectionArgs is null; "); 340 } else if (selectionArgs.length == 0) { 341 sb.append("selectionArgs is empty; "); 342 } else { 343 for (int i = 0; i < selectionArgs.length; ++i) { 344 sb.append("selectionArgs["); 345 sb.append(i); 346 sb.append("] is "); 347 sb.append(selectionArgs[i]); 348 sb.append("; "); 349 } 350 } 351 sb.append("sort is "); 352 sb.append(sortOrder); 353 sb.append("."); 354 Log.v(TAG, sb.toString()); 355 } 356 357 Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); 358 359 if (ret != null) { 360 ret.setNotificationUri(getContext().getContentResolver(), uri); 361 if (V) Log.v(TAG, "created cursor " + ret + " on behalf of ");// + 362 } else { 363 if (D) Log.d(TAG, "query failed in downloads database"); 364 } 365 366 return ret; 367 } 368 369 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)370 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 371 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 372 373 int count; 374 long rowId = 0; 375 376 int match = sURIMatcher.match(uri); 377 switch (match) { 378 case SHARES: 379 case SHARES_ID: { 380 String myWhere; 381 if (selection != null) { 382 if (match == SHARES) { 383 myWhere = "( " + selection + " )"; 384 } else { 385 myWhere = "( " + selection + " ) AND "; 386 } 387 } else { 388 myWhere = ""; 389 } 390 if (match == SHARES_ID) { 391 String segment = uri.getPathSegments().get(1); 392 rowId = Long.parseLong(segment); 393 myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; 394 } 395 396 if (values.size() > 0) { 397 count = db.update(DB_TABLE, values, myWhere, selectionArgs); 398 } else { 399 count = 0; 400 } 401 break; 402 } 403 default: { 404 if (D) Log.d(TAG, "updating unknown/invalid URI: " + uri); 405 throw new UnsupportedOperationException("Cannot update URI: " + uri); 406 } 407 } 408 getContext().getContentResolver().notifyChange(uri, null); 409 410 return count; 411 } 412 413 @Override delete(Uri uri, String selection, String[] selectionArgs)414 public int delete(Uri uri, String selection, String[] selectionArgs) { 415 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 416 int count; 417 int match = sURIMatcher.match(uri); 418 switch (match) { 419 case SHARES: 420 case SHARES_ID: { 421 String myWhere; 422 if (selection != null) { 423 if (match == SHARES) { 424 myWhere = "( " + selection + " )"; 425 } else { 426 myWhere = "( " + selection + " ) AND "; 427 } 428 } else { 429 myWhere = ""; 430 } 431 if (match == SHARES_ID) { 432 String segment = uri.getPathSegments().get(1); 433 long rowId = Long.parseLong(segment); 434 myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; 435 } 436 437 count = db.delete(DB_TABLE, myWhere, selectionArgs); 438 break; 439 } 440 default: { 441 if (D) Log.d(TAG, "deleting unknown/invalid URI: " + uri); 442 throw new UnsupportedOperationException("Cannot delete URI: " + uri); 443 } 444 } 445 getContext().getContentResolver().notifyChange(uri, null); 446 return count; 447 } 448 } 449