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