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