/* * 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.HashMap; /** * 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); 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; } }