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