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