| /* |
| * Copyright (c) 2008-2009, Motorola, Inc. |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * - Neither the name of the Motorola, Inc. nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package com.android.bluetooth.opp; |
| |
| import android.content.ContentProvider; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.database.SQLException; |
| import android.content.UriMatcher; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.database.sqlite.SQLiteOpenHelper; |
| import android.database.sqlite.SQLiteQueryBuilder; |
| import android.net.Uri; |
| import android.provider.LiveFolders; |
| import android.util.Log; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * This provider allows application to interact with Bluetooth OPP manager |
| */ |
| |
| public final class BluetoothOppProvider extends ContentProvider { |
| |
| private static final String TAG = "BluetoothOppProvider"; |
| private static final boolean D = Constants.DEBUG; |
| private static final boolean V = Constants.VERBOSE; |
| |
| /** Database filename */ |
| private static final String DB_NAME = "btopp.db"; |
| |
| /** Current database version */ |
| private static final int DB_VERSION = 1; |
| |
| /** Database version from which upgrading is a nop */ |
| private static final int DB_VERSION_NOP_UPGRADE_FROM = 0; |
| |
| /** Database version to which upgrading is a nop */ |
| private static final int DB_VERSION_NOP_UPGRADE_TO = 1; |
| |
| /** Name of table in the database */ |
| private static final String DB_TABLE = "btopp"; |
| |
| /** MIME type for the entire share list */ |
| private static final String SHARE_LIST_TYPE = "vnd.android.cursor.dir/vnd.android.btopp"; |
| |
| /** MIME type for an individual share */ |
| private static final String SHARE_TYPE = "vnd.android.cursor.item/vnd.android.btopp"; |
| |
| /** URI matcher used to recognize URIs sent by applications */ |
| private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); |
| |
| /** URI matcher constant for the URI of the entire share list */ |
| private static final int SHARES = 1; |
| |
| /** URI matcher constant for the URI of an individual share */ |
| private static final int SHARES_ID = 2; |
| |
| /** URI matcher constant for the URI of live folder */ |
| private static final int LIVE_FOLDER_RECEIVED_FILES = 3; |
| static { |
| sURIMatcher.addURI("com.android.bluetooth.opp", "btopp", SHARES); |
| sURIMatcher.addURI("com.android.bluetooth.opp", "btopp/#", SHARES_ID); |
| sURIMatcher.addURI("com.android.bluetooth.opp", "live_folders/received", |
| LIVE_FOLDER_RECEIVED_FILES); |
| } |
| |
| private static final HashMap<String, String> LIVE_FOLDER_PROJECTION_MAP; |
| static { |
| LIVE_FOLDER_PROJECTION_MAP = new HashMap<String, String>(); |
| LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders._ID, BluetoothShare._ID + " AS " |
| + LiveFolders._ID); |
| LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.NAME, BluetoothShare.FILENAME_HINT + " AS " |
| + LiveFolders.NAME); |
| } |
| |
| /** The database that lies underneath this content provider */ |
| private SQLiteOpenHelper mOpenHelper = null; |
| |
| /** |
| * Creates and updated database on demand when opening it. Helper class to |
| * create database the first time the provider is initialized and upgrade it |
| * when a new version of the provider needs an updated version of the |
| * database. |
| */ |
| private final class DatabaseHelper extends SQLiteOpenHelper { |
| |
| public DatabaseHelper(final Context context) { |
| super(context, DB_NAME, null, DB_VERSION); |
| } |
| |
| /** |
| * Creates database the first time we try to open it. |
| */ |
| @Override |
| public void onCreate(final SQLiteDatabase db) { |
| if (V) Log.v(TAG, "populating new database"); |
| createTable(db); |
| } |
| |
| //TODO: use this function to check garbage transfer left in db, for example, |
| // a crash incoming file |
| /* |
| * (not a javadoc comment) Checks data integrity when opening the |
| * database. |
| */ |
| /* |
| * @Override public void onOpen(final SQLiteDatabase db) { |
| * super.onOpen(db); } |
| */ |
| |
| /** |
| * Updates the database format when a content provider is used with a |
| * database that was created with a different format. |
| */ |
| // Note: technically, this could also be a downgrade, so if we want |
| // to gracefully handle upgrades we should be careful about |
| // what to do on downgrades. |
| @Override |
| public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) { |
| if (oldV == DB_VERSION_NOP_UPGRADE_FROM) { |
| if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op |
| // upgrade. |
| return; |
| } |
| // NOP_FROM and NOP_TO are identical, just in different |
| // codelines. Upgrading |
| // from NOP_FROM is the same as upgrading from NOP_TO. |
| oldV = DB_VERSION_NOP_UPGRADE_TO; |
| } |
| Log.i(TAG, "Upgrading downloads database from version " + oldV + " to " |
| + newV + ", which will destroy all old data"); |
| dropTable(db); |
| createTable(db); |
| } |
| |
| } |
| |
| private void createTable(SQLiteDatabase db) { |
| try { |
| db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID |
| + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, " |
| + BluetoothShare.FILENAME_HINT + " TEXT, " + BluetoothShare._DATA + " TEXT, " |
| + BluetoothShare.MIMETYPE + " TEXT, " + BluetoothShare.DIRECTION + " INTEGER, " |
| + BluetoothShare.DESTINATION + " TEXT, " + BluetoothShare.VISIBILITY |
| + " INTEGER, " + BluetoothShare.USER_CONFIRMATION + " INTEGER, " |
| + BluetoothShare.STATUS + " INTEGER, " + BluetoothShare.TOTAL_BYTES |
| + " INTEGER, " + BluetoothShare.CURRENT_BYTES + " INTEGER, " |
| + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED |
| + " INTEGER); "); |
| } catch (SQLException ex) { |
| Log.e(TAG, "couldn't create table in downloads database"); |
| throw ex; |
| } |
| } |
| |
| private void dropTable(SQLiteDatabase db) { |
| try { |
| db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); |
| } catch (SQLException ex) { |
| Log.e(TAG, "couldn't drop table in downloads database"); |
| throw ex; |
| } |
| } |
| |
| @Override |
| public String getType(Uri uri) { |
| int match = sURIMatcher.match(uri); |
| switch (match) { |
| case SHARES: { |
| return SHARE_LIST_TYPE; |
| } |
| case SHARES_ID: { |
| return SHARE_TYPE; |
| } |
| default: { |
| if (D) Log.d(TAG, "calling getType on an unknown URI: " + uri); |
| throw new IllegalArgumentException("Unknown URI: " + uri); |
| } |
| } |
| } |
| |
| private static final void copyString(String key, ContentValues from, ContentValues to) { |
| String s = from.getAsString(key); |
| if (s != null) { |
| to.put(key, s); |
| } |
| } |
| |
| private static final void copyInteger(String key, ContentValues from, ContentValues to) { |
| Integer i = from.getAsInteger(key); |
| if (i != null) { |
| to.put(key, i); |
| } |
| } |
| |
| @Override |
| public Uri insert(Uri uri, ContentValues values) { |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| |
| if (sURIMatcher.match(uri) != SHARES) { |
| if (D) Log.d(TAG, "calling insert on an unknown/invalid URI: " + uri); |
| throw new IllegalArgumentException("Unknown/Invalid URI " + uri); |
| } |
| |
| ContentValues filteredValues = new ContentValues(); |
| |
| copyString(BluetoothShare.URI, values, filteredValues); |
| copyString(BluetoothShare.FILENAME_HINT, values, filteredValues); |
| copyString(BluetoothShare.MIMETYPE, values, filteredValues); |
| copyString(BluetoothShare.DESTINATION, values, filteredValues); |
| |
| copyInteger(BluetoothShare.VISIBILITY, values, filteredValues); |
| copyInteger(BluetoothShare.TOTAL_BYTES, values, filteredValues); |
| |
| if (values.getAsInteger(BluetoothShare.VISIBILITY) == null) { |
| filteredValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_VISIBLE); |
| } |
| Integer dir = values.getAsInteger(BluetoothShare.DIRECTION); |
| Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION); |
| String address = values.getAsString(BluetoothShare.DESTINATION); |
| |
| if (values.getAsInteger(BluetoothShare.DIRECTION) == null) { |
| dir = BluetoothShare.DIRECTION_OUTBOUND; |
| } |
| if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) { |
| con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED; |
| } |
| if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) { |
| con = BluetoothShare.USER_CONFIRMATION_PENDING; |
| } |
| filteredValues.put(BluetoothShare.USER_CONFIRMATION, con); |
| filteredValues.put(BluetoothShare.DIRECTION, dir); |
| |
| filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING); |
| filteredValues.put(Constants.MEDIA_SCANNED, 0); |
| |
| Long ts = values.getAsLong(BluetoothShare.TIMESTAMP); |
| if (ts == null) { |
| ts = System.currentTimeMillis(); |
| } |
| filteredValues.put(BluetoothShare.TIMESTAMP, ts); |
| |
| Context context = getContext(); |
| context.startService(new Intent(context, BluetoothOppService.class)); |
| |
| long rowID = db.insert(DB_TABLE, null, filteredValues); |
| |
| Uri ret = null; |
| |
| if (rowID != -1) { |
| context.startService(new Intent(context, BluetoothOppService.class)); |
| ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID); |
| context.getContentResolver().notifyChange(uri, null); |
| } else { |
| if (D) Log.d(TAG, "couldn't insert into btopp database"); |
| } |
| |
| return ret; |
| } |
| |
| @Override |
| public boolean onCreate() { |
| mOpenHelper = new DatabaseHelper(getContext()); |
| return true; |
| } |
| |
| @Override |
| public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, |
| String sortOrder) { |
| SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| |
| SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); |
| |
| int match = sURIMatcher.match(uri); |
| switch (match) { |
| case SHARES: { |
| qb.setTables(DB_TABLE); |
| break; |
| } |
| case SHARES_ID: { |
| qb.setTables(DB_TABLE); |
| qb.appendWhere(BluetoothShare._ID + "="); |
| qb.appendWhere(uri.getPathSegments().get(1)); |
| break; |
| } |
| case LIVE_FOLDER_RECEIVED_FILES: { |
| qb.setTables(DB_TABLE); |
| qb.setProjectionMap(LIVE_FOLDER_PROJECTION_MAP); |
| qb.appendWhere(BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND |
| + " AND " + BluetoothShare.STATUS + "=" + BluetoothShare.STATUS_SUCCESS); |
| sortOrder = "_id DESC, " + sortOrder; |
| break; |
| } |
| default: { |
| if (D) Log.d(TAG, "querying unknown URI: " + uri); |
| throw new IllegalArgumentException("Unknown URI: " + uri); |
| } |
| } |
| |
| if (V) { |
| java.lang.StringBuilder sb = new java.lang.StringBuilder(); |
| sb.append("starting query, database is "); |
| if (db != null) { |
| sb.append("not "); |
| } |
| sb.append("null; "); |
| if (projection == null) { |
| sb.append("projection is null; "); |
| } else if (projection.length == 0) { |
| sb.append("projection is empty; "); |
| } else { |
| for (int i = 0; i < projection.length; ++i) { |
| sb.append("projection["); |
| sb.append(i); |
| sb.append("] is "); |
| sb.append(projection[i]); |
| sb.append("; "); |
| } |
| } |
| sb.append("selection is "); |
| sb.append(selection); |
| sb.append("; "); |
| if (selectionArgs == null) { |
| sb.append("selectionArgs is null; "); |
| } else if (selectionArgs.length == 0) { |
| sb.append("selectionArgs is empty; "); |
| } else { |
| for (int i = 0; i < selectionArgs.length; ++i) { |
| sb.append("selectionArgs["); |
| sb.append(i); |
| sb.append("] is "); |
| sb.append(selectionArgs[i]); |
| sb.append("; "); |
| } |
| } |
| sb.append("sort is "); |
| sb.append(sortOrder); |
| sb.append("."); |
| Log.v(TAG, sb.toString()); |
| } |
| |
| Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); |
| |
| if (ret != null) { |
| ret.setNotificationUri(getContext().getContentResolver(), uri); |
| if (V) Log.v(TAG, "created cursor " + ret + " on behalf of ");// + |
| } else { |
| if (D) Log.d(TAG, "query failed in downloads database"); |
| } |
| |
| return ret; |
| } |
| |
| @Override |
| public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| |
| int count; |
| long rowId = 0; |
| |
| int match = sURIMatcher.match(uri); |
| switch (match) { |
| case SHARES: |
| case SHARES_ID: { |
| String myWhere; |
| if (selection != null) { |
| if (match == SHARES) { |
| myWhere = "( " + selection + " )"; |
| } else { |
| myWhere = "( " + selection + " ) AND "; |
| } |
| } else { |
| myWhere = ""; |
| } |
| if (match == SHARES_ID) { |
| String segment = uri.getPathSegments().get(1); |
| rowId = Long.parseLong(segment); |
| myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; |
| } |
| |
| if (values.size() > 0) { |
| count = db.update(DB_TABLE, values, myWhere, selectionArgs); |
| } else { |
| count = 0; |
| } |
| break; |
| } |
| default: { |
| if (D) Log.d(TAG, "updating unknown/invalid URI: " + uri); |
| throw new UnsupportedOperationException("Cannot update URI: " + uri); |
| } |
| } |
| getContext().getContentResolver().notifyChange(uri, null); |
| |
| return count; |
| } |
| |
| @Override |
| public int delete(Uri uri, String selection, String[] selectionArgs) { |
| SQLiteDatabase db = mOpenHelper.getWritableDatabase(); |
| int count; |
| int match = sURIMatcher.match(uri); |
| switch (match) { |
| case SHARES: |
| case SHARES_ID: { |
| String myWhere; |
| if (selection != null) { |
| if (match == SHARES) { |
| myWhere = "( " + selection + " )"; |
| } else { |
| myWhere = "( " + selection + " ) AND "; |
| } |
| } else { |
| myWhere = ""; |
| } |
| if (match == SHARES_ID) { |
| String segment = uri.getPathSegments().get(1); |
| long rowId = Long.parseLong(segment); |
| myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) "; |
| } |
| |
| count = db.delete(DB_TABLE, myWhere, selectionArgs); |
| break; |
| } |
| default: { |
| if (D) Log.d(TAG, "deleting unknown/invalid URI: " + uri); |
| throw new UnsupportedOperationException("Cannot delete URI: " + uri); |
| } |
| } |
| getContext().getContentResolver().notifyChange(uri, null); |
| return count; |
| } |
| } |