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