BluetoothOppProvider.java revision bde9072307960ac90f44bfdc9dc80ee84f00bf18
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 33package com.android.bluetooth.opp; 34 35import android.content.ContentProvider; 36import android.content.ContentValues; 37import android.content.Context; 38import android.content.Intent; 39import android.database.Cursor; 40import android.database.SQLException; 41import android.content.UriMatcher; 42import android.database.sqlite.SQLiteDatabase; 43import android.database.sqlite.SQLiteOpenHelper; 44import android.database.sqlite.SQLiteQueryBuilder; 45import android.net.Uri; 46import android.util.Log; 47 48/** 49 * This provider allows application to interact with Bluetooth OPP manager 50 */ 51 52public 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 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 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 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 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 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 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 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 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 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 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 String address = values.getAsString(BluetoothShare.DESTINATION); 244 245 if (values.getAsInteger(BluetoothShare.DIRECTION) == null) { 246 dir = BluetoothShare.DIRECTION_OUTBOUND; 247 } 248 if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) { 249 con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED; 250 } 251 if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) { 252 con = BluetoothShare.USER_CONFIRMATION_PENDING; 253 } 254 filteredValues.put(BluetoothShare.USER_CONFIRMATION, con); 255 filteredValues.put(BluetoothShare.DIRECTION, dir); 256 257 filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING); 258 filteredValues.put(Constants.MEDIA_SCANNED, 0); 259 260 Long ts = values.getAsLong(BluetoothShare.TIMESTAMP); 261 if (ts == null) { 262 ts = System.currentTimeMillis(); 263 } 264 filteredValues.put(BluetoothShare.TIMESTAMP, ts); 265 266 Context context = getContext(); 267 context.startService(new Intent(context, BluetoothOppService.class)); 268 269 long rowID = db.insert(DB_TABLE, null, filteredValues); 270 271 Uri ret = null; 272 273 if (rowID != -1) { 274 context.startService(new Intent(context, BluetoothOppService.class)); 275 ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID); 276 context.getContentResolver().notifyChange(uri, null); 277 } else { 278 if (D) Log.d(TAG, "couldn't insert into btopp database"); 279 } 280 281 return ret; 282 } 283 284 @Override 285 public boolean onCreate() { 286 mOpenHelper = new DatabaseHelper(getContext()); 287 return true; 288 } 289 290 @Override 291 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 292 String sortOrder) { 293 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 294 295 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 296 qb.setStrict(true); 297 298 int match = sURIMatcher.match(uri); 299 switch (match) { 300 case SHARES: { 301 qb.setTables(DB_TABLE); 302 break; 303 } 304 case SHARES_ID: { 305 qb.setTables(DB_TABLE); 306 qb.appendWhere(BluetoothShare._ID + "="); 307 qb.appendWhere(uri.getPathSegments().get(1)); 308 break; 309 } 310 default: { 311 if (D) Log.d(TAG, "querying unknown URI: " + uri); 312 throw new IllegalArgumentException("Unknown URI: " + uri); 313 } 314 } 315 316 if (V) { 317 java.lang.StringBuilder sb = new java.lang.StringBuilder(); 318 sb.append("starting query, database is "); 319 if (db != null) { 320 sb.append("not "); 321 } 322 sb.append("null; "); 323 if (projection == null) { 324 sb.append("projection is null; "); 325 } else if (projection.length == 0) { 326 sb.append("projection is empty; "); 327 } else { 328 for (int i = 0; i < projection.length; ++i) { 329 sb.append("projection["); 330 sb.append(i); 331 sb.append("] is "); 332 sb.append(projection[i]); 333 sb.append("; "); 334 } 335 } 336 sb.append("selection is "); 337 sb.append(selection); 338 sb.append("; "); 339 if (selectionArgs == null) { 340 sb.append("selectionArgs is null; "); 341 } else if (selectionArgs.length == 0) { 342 sb.append("selectionArgs is empty; "); 343 } else { 344 for (int i = 0; i < selectionArgs.length; ++i) { 345 sb.append("selectionArgs["); 346 sb.append(i); 347 sb.append("] is "); 348 sb.append(selectionArgs[i]); 349 sb.append("; "); 350 } 351 } 352 sb.append("sort is "); 353 sb.append(sortOrder); 354 sb.append("."); 355 Log.v(TAG, sb.toString()); 356 } 357 358 Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); 359 360 if (ret != null) { 361 ret.setNotificationUri(getContext().getContentResolver(), uri); 362 if (V) Log.v(TAG, "created cursor " + ret + " on behalf of ");// + 363 } else { 364 if (D) Log.d(TAG, "query failed in downloads database"); 365 } 366 367 return ret; 368 } 369 370 @Override 371 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 372 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 373 374 int count; 375 long rowId = 0; 376 377 int match = sURIMatcher.match(uri); 378 switch (match) { 379 case SHARES: 380 case SHARES_ID: { 381 String myWhere; 382 if (selection != null) { 383 if (match == SHARES) { 384 myWhere = "( " + selection + " )"; 385 } else { 386 myWhere = "( " + selection + " ) AND "; 387 } 388 } else { 389 myWhere = ""; 390 } 391 if (match == SHARES_ID) { 392 String segment = uri.getPathSegments().get(1); 393 rowId = Long.parseLong(segment); 394 myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; 395 } 396 397 if (values.size() > 0) { 398 count = db.update(DB_TABLE, values, myWhere, selectionArgs); 399 } else { 400 count = 0; 401 } 402 break; 403 } 404 default: { 405 if (D) Log.d(TAG, "updating unknown/invalid URI: " + uri); 406 throw new UnsupportedOperationException("Cannot update URI: " + uri); 407 } 408 } 409 getContext().getContentResolver().notifyChange(uri, null); 410 411 return count; 412 } 413 414 @Override 415 public int delete(Uri uri, String selection, String[] selectionArgs) { 416 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 417 int count; 418 int match = sURIMatcher.match(uri); 419 switch (match) { 420 case SHARES: 421 case SHARES_ID: { 422 String myWhere; 423 if (selection != null) { 424 if (match == SHARES) { 425 myWhere = "( " + selection + " )"; 426 } else { 427 myWhere = "( " + selection + " ) AND "; 428 } 429 } else { 430 myWhere = ""; 431 } 432 if (match == SHARES_ID) { 433 String segment = uri.getPathSegments().get(1); 434 long rowId = Long.parseLong(segment); 435 myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; 436 } 437 438 count = db.delete(DB_TABLE, myWhere, selectionArgs); 439 break; 440 } 441 default: { 442 if (D) Log.d(TAG, "deleting unknown/invalid URI: " + uri); 443 throw new UnsupportedOperationException("Cannot delete URI: " + uri); 444 } 445 } 446 getContext().getContentResolver().notifyChange(uri, null); 447 return count; 448 } 449} 450