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