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