3b8582571d3f99d2dcb4f0b0f68929c31aaf2e9b
[android/platform/packages/apps/Tag.git] / src / com / android / apps / tag / provider / TagProvider.java
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16
17 package com.android.apps.tag.provider;
18
19 import com.android.apps.tag.R;
20 import com.android.apps.tag.provider.TagContract.NdefMessages;
21 import com.google.common.base.Charsets;
22 import com.google.common.base.Preconditions;
23 import com.google.common.collect.ImmutableMap;
24
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.UriMatcher;
29 import android.database.Cursor;
30 import android.database.sqlite.SQLiteDatabase;
31 import android.database.sqlite.SQLiteOpenHelper;
32 import android.database.sqlite.SQLiteQueryBuilder;
33 import android.net.Uri;
34 import android.nfc.FormatException;
35 import android.nfc.NdefMessage;
36 import android.nfc.NdefRecord;
37 import android.os.AsyncTask;
38 import android.os.ParcelFileDescriptor;
39 import android.text.TextUtils;
40 import android.util.Log;
41
42 import java.io.FileNotFoundException;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.Map;
46
47 /**
48  * Stores NFC tags in a database. The contract is defined in {@link TagContract}.
49  */
50 public class TagProvider extends SQLiteContentProvider implements TagProviderPipeDataWriter {
51     private static final String TAG = "TagProvider";
52
53     private static final int NDEF_MESSAGES = 1000;
54     private static final int NDEF_MESSAGES_ID = 1001;
55
56     private static final int NDEF_MESSAGES_ID_MIME = 2002;
57
58     private static final UriMatcher MATCHER;
59
60
61     private static final Map<String, String> NDEF_MESSAGES_PROJECTION_MAP =
62             ImmutableMap.<String, String>builder()
63                 .put(NdefMessages._ID, NdefMessages._ID)
64                 .put(NdefMessages.TITLE, NdefMessages.TITLE)
65                 .put(NdefMessages.BYTES, NdefMessages.BYTES)
66                 .put(NdefMessages.DATE, NdefMessages.DATE)
67                 .put(NdefMessages.STARRED, NdefMessages.STARRED)
68                 .build();
69
70     private Map<String, String> mNdefRecordsMimeProjectionMap;
71
72     static {
73         MATCHER = new UriMatcher(0);
74         String auth = TagContract.AUTHORITY;
75
76         MATCHER.addURI(auth, "ndef_msgs", NDEF_MESSAGES);
77         MATCHER.addURI(auth, "ndef_msgs/#", NDEF_MESSAGES_ID);
78         MATCHER.addURI(auth, "ndef_msgs/#/#/mime", NDEF_MESSAGES_ID_MIME);
79
80     }
81
82     @Override
83     public boolean onCreate() {
84         boolean result = super.onCreate();
85
86         // Build the projection map for the MIME records using a localized display name
87         mNdefRecordsMimeProjectionMap = ImmutableMap.<String, String>builder()
88                 .put(NdefMessages.MIME._ID, NdefMessages.MIME._ID)
89                 .put(NdefMessages.MIME.SIZE, NdefMessages.MIME.SIZE)
90                 .put(NdefMessages.MIME.DISPLAY_NAME,
91                         "'" + getContext().getString(R.string.mime_display_name) + "' AS "
92                         + NdefMessages.MIME.DISPLAY_NAME)
93                 .build();
94
95         return result;
96     }
97
98     @Override
99     protected SQLiteOpenHelper getDatabaseHelper(Context context) {
100         return new TagDBHelper(context);
101     }
102
103     /**
104      * Appends one set of selection args to another. This is useful when adding a selection
105      * argument to a user provided set.
106      */
107     public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
108         if (originalValues == null || originalValues.length == 0) {
109             return newValues;
110         }
111         String[] result = new String[originalValues.length + newValues.length ];
112         System.arraycopy(originalValues, 0, result, 0, originalValues.length);
113         System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
114         return result;
115     }
116
117     /**
118      * Concatenates two SQL WHERE clauses, handling empty or null values.
119      */
120     public static String concatenateWhere(String a, String b) {
121         if (TextUtils.isEmpty(a)) {
122             return b;
123         }
124         if (TextUtils.isEmpty(b)) {
125             return a;
126         }
127
128         return "(" + a + ") AND (" + b + ")";
129     }
130
131     @Override
132     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
133             String sortOrder) {
134         SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
135         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
136         int match = MATCHER.match(uri);
137         switch (match) {
138             case NDEF_MESSAGES_ID: {
139                 selection = concatenateWhere(selection,
140                         TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
141                 selectionArgs = appendSelectionArgs(selectionArgs,
142                         new String[] { Long.toString(ContentUris.parseId(uri)) });
143                 // fall through
144             }
145             case NDEF_MESSAGES: {
146                 qb.setTables(TagDBHelper.TABLE_NAME_NDEF_MESSAGES);
147                 qb.setProjectionMap(NDEF_MESSAGES_PROJECTION_MAP);
148                 break;
149             }
150
151             case NDEF_MESSAGES_ID_MIME: {
152                 selection = concatenateWhere(selection,
153                         TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
154                 selectionArgs = appendSelectionArgs(selectionArgs,
155                         new String[] { Long.toString(ContentUris.parseId(uri)) });
156                 qb.setTables(TagDBHelper.TABLE_NAME_NDEF_MESSAGES);
157                 qb.setProjectionMap(mNdefRecordsMimeProjectionMap);
158                 break;
159             }
160
161             default: {
162                 throw new IllegalArgumentException("unkown uri " + uri);
163             }
164         }
165
166         Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
167         if (cursor != null) {
168             cursor.setNotificationUri(getContext().getContentResolver(), TagContract.AUTHORITY_URI);
169         }
170         return cursor;
171     }
172
173     @Override
174     protected Uri insertInTransaction(Uri uri, ContentValues values) {
175         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
176         int match = MATCHER.match(uri);
177         long id = -1;
178         switch (match) {
179             case NDEF_MESSAGES: {
180                 id = db.insert(TagDBHelper.TABLE_NAME_NDEF_MESSAGES, NdefMessages.TITLE, values);
181                 break;
182             }
183
184             default: {
185                 throw new IllegalArgumentException("unkown uri " + uri);
186             }
187         }
188
189         if (id >= 0) {
190             return ContentUris.withAppendedId(uri, id);
191         }
192         return null;
193     }
194
195     @Override
196     protected int updateInTransaction(Uri uri, ContentValues values, String selection,
197             String[] selectionArgs) {
198         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
199         int match = MATCHER.match(uri);
200         int count = 0;
201         switch (match) {
202             case NDEF_MESSAGES_ID: {
203                 selection = concatenateWhere(selection,
204                         TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
205                 selectionArgs = appendSelectionArgs(selectionArgs,
206                         new String[] { Long.toString(ContentUris.parseId(uri)) });
207                 // fall through
208             }
209             case NDEF_MESSAGES: {
210                 count = db.update(TagDBHelper.TABLE_NAME_NDEF_MESSAGES, values, selection,
211                         selectionArgs);
212                 break;
213             }
214
215             default: {
216                 throw new IllegalArgumentException("unkown uri " + uri);
217             }
218         }
219
220         return count;
221     }
222
223     @Override
224     protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
225         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
226         int match = MATCHER.match(uri);
227         int count = 0;
228         switch (match) {
229             case NDEF_MESSAGES_ID: {
230                 selection = concatenateWhere(selection,
231                         TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
232                 selectionArgs = appendSelectionArgs(selectionArgs,
233                         new String[] { Long.toString(ContentUris.parseId(uri)) });
234                 // fall through
235             }
236             case NDEF_MESSAGES: {
237                 count = db.delete(TagDBHelper.TABLE_NAME_NDEF_MESSAGES, selection, selectionArgs);
238                 break;
239             }
240
241             default: {
242                 throw new IllegalArgumentException("unkown uri " + uri);
243             }
244         }
245
246         return count;
247     }
248
249     private NdefRecord getRecord(Uri uri) {
250         NdefRecord record = null;
251
252         Cursor cursor = null;
253         try {
254             SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
255             cursor = db.query(TagDBHelper.TABLE_NAME_NDEF_MESSAGES,
256                     new String[] { NdefMessages.BYTES }, "_id=?",
257                     new String[] { uri.getPathSegments().get(1) }, null, null, null, null);
258             if (cursor.moveToFirst()) {
259                 NdefMessage msg = new NdefMessage(cursor.getBlob(0));
260                 NdefRecord[] records = msg.getRecords();
261
262                 int offset = Integer.parseInt(uri.getPathSegments().get(2));
263
264                 if (records != null && offset < records.length) {
265                     record = records[offset];
266                 }
267             }
268         } catch (FormatException e) {
269             Log.e(TAG, "Invalid NdefMessage format", e);
270         } finally {
271             if (cursor != null) cursor.close();
272         }
273
274         return record;
275     }
276
277
278     @Override
279     public String getType(Uri uri) {
280         int match = MATCHER.match(uri);
281         switch (match) {
282
283             case NDEF_MESSAGES_ID: {
284                 return NdefMessages.CONTENT_ITEM_TYPE;
285             }
286             case NDEF_MESSAGES: {
287                 return NdefMessages.CONTENT_TYPE;
288             }
289
290             case NDEF_MESSAGES_ID_MIME: {
291                 NdefRecord record = getRecord(uri);
292                 if (record != null) {
293                     return new String(record.getType(), Charsets.US_ASCII).toLowerCase();
294                 }
295                 return null;
296             }
297
298             default: {
299                 throw new IllegalArgumentException("unknown uri " + uri);
300             }
301         }
302     }
303
304     @Override
305     protected void notifyChange() {
306         getContext().getContentResolver().notifyChange(TagContract.AUTHORITY_URI, null, false);
307     }
308
309     @Override
310     public void writeMimeDataToPipe(ParcelFileDescriptor output, Uri uri) {
311         NdefRecord record = getRecord(uri);
312         if (record == null) return;
313
314         try {
315             byte[] data = record.getPayload();
316             FileOutputStream os = new FileOutputStream(output.getFileDescriptor());
317             os.write(data);
318             os.flush();
319         } catch (IOException e) {
320             Log.e(TAG, "failed to write MIME data to " + uri, e);
321         }
322     }
323
324     /**
325      * A helper function for implementing {@link #openFile}, for
326      * creating a data pipe and background thread allowing you to stream
327      * generated data back to the client.  This function returns a new
328      * ParcelFileDescriptor that should be returned to the caller (the caller
329      * is responsible for closing it).
330      *
331      * @param uri The URI whose data is to be written.
332      * @param func Interface implementing the function that will actually
333      * stream the data.
334      * @return Returns a new ParcelFileDescriptor holding the read side of
335      * the pipe.  This should be returned to the caller for reading; the caller
336      * is responsible for closing it when done.
337      */
338     public ParcelFileDescriptor openMimePipe(final Uri uri,
339             final TagProviderPipeDataWriter func) throws FileNotFoundException {
340         try {
341             final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
342
343             AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
344                 @Override
345                 protected Object doInBackground(Object... params) {
346                     func.writeMimeDataToPipe(fds[1], uri);
347                     try {
348                         fds[1].close();
349                     } catch (IOException e) {
350                         Log.w(TAG, "Failure closing pipe", e);
351                     }
352                     return null;
353                 }
354             };
355             task.execute((Object[])null);
356
357             return fds[0];
358         } catch (IOException e) {
359             throw new FileNotFoundException("failure making pipe");
360         }
361     }
362
363     @Override
364     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
365         Preconditions.checkArgument("r".equals(mode));
366         Preconditions.checkArgument(MATCHER.match(uri) == NDEF_MESSAGES_ID_MIME);
367         return openMimePipe(uri, this);
368     }
369 }