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