• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     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 
DatabaseHelper(final Context context)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
onCreate(final SQLiteDatabase db)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
onUpgrade(final SQLiteDatabase db, int oldV, final int newV)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 
createTable(SQLiteDatabase db)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 
dropTable(SQLiteDatabase db)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
getType(Uri uri)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 
copyString(String key, ContentValues from, ContentValues to)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 
copyInteger(String key, ContentValues from, ContentValues to)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 
copyLong(String key, ContentValues from, ContentValues to)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
insert(Uri uri, ContentValues values)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
onCreate()290     public boolean onCreate() {
291         mOpenHelper = new DatabaseHelper(getContext());
292         return true;
293     }
294 
295     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)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
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)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
delete(Uri uri, String selection, String[] selectionArgs)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