blob: 1fd915386acca8e356f7a900e9d867244ad1938b [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.apps.tag.provider;
import com.android.apps.tag.R;
import com.android.apps.tag.provider.TagContract.NdefMessages;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.os.AsyncTask;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
/**
* Stores NFC tags in a database. The contract is defined in {@link TagContract}.
*/
public class TagProvider extends SQLiteContentProvider implements TagProviderPipeDataWriter {
private static final String TAG = "TagProvider";
private static final int NDEF_MESSAGES = 1000;
private static final int NDEF_MESSAGES_ID = 1001;
private static final int NDEF_MESSAGES_ID_MIME = 2002;
private static final UriMatcher MATCHER;
private static final Map<String, String> NDEF_MESSAGES_PROJECTION_MAP =
ImmutableMap.<String, String>builder()
.put(NdefMessages._ID, NdefMessages._ID)
.put(NdefMessages.TITLE, NdefMessages.TITLE)
.put(NdefMessages.BYTES, NdefMessages.BYTES)
.put(NdefMessages.DATE, NdefMessages.DATE)
.put(NdefMessages.STARRED, NdefMessages.STARRED)
.build();
private Map<String, String> mNdefRecordsMimeProjectionMap;
static {
MATCHER = new UriMatcher(0);
String auth = TagContract.AUTHORITY;
MATCHER.addURI(auth, "ndef_msgs", NDEF_MESSAGES);
MATCHER.addURI(auth, "ndef_msgs/#", NDEF_MESSAGES_ID);
MATCHER.addURI(auth, "ndef_msgs/#/#/mime", NDEF_MESSAGES_ID_MIME);
}
@Override
public boolean onCreate() {
boolean result = super.onCreate();
// Build the projection map for the MIME records using a localized display name
mNdefRecordsMimeProjectionMap = ImmutableMap.<String, String>builder()
.put(NdefMessages.MIME._ID, NdefMessages.MIME._ID)
.put(NdefMessages.MIME.SIZE, NdefMessages.MIME.SIZE)
.put(NdefMessages.MIME.DISPLAY_NAME,
"'" + getContext().getString(R.string.mime_display_name) + "' AS "
+ NdefMessages.MIME.DISPLAY_NAME)
.build();
return result;
}
@Override
protected SQLiteOpenHelper getDatabaseHelper(Context context) {
return new TagDBHelper(context);
}
/**
* Appends one set of selection args to another. This is useful when adding a selection
* argument to a user provided set.
*/
public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
if (originalValues == null || originalValues.length == 0) {
return newValues;
}
String[] result = new String[originalValues.length + newValues.length ];
System.arraycopy(originalValues, 0, result, 0, originalValues.length);
System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
return result;
}
/**
* Concatenates two SQL WHERE clauses, handling empty or null values.
*/
public static String concatenateWhere(String a, String b) {
if (TextUtils.isEmpty(a)) {
return b;
}
if (TextUtils.isEmpty(b)) {
return a;
}
return "(" + a + ") AND (" + b + ")";
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = MATCHER.match(uri);
switch (match) {
case NDEF_MESSAGES_ID: {
selection = concatenateWhere(selection,
TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
}
case NDEF_MESSAGES: {
qb.setTables(TagDBHelper.TABLE_NAME_NDEF_MESSAGES);
qb.setProjectionMap(NDEF_MESSAGES_PROJECTION_MAP);
break;
}
case NDEF_MESSAGES_ID_MIME: {
selection = concatenateWhere(selection,
TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
qb.setTables(TagDBHelper.TABLE_NAME_NDEF_MESSAGES);
qb.setProjectionMap(mNdefRecordsMimeProjectionMap);
break;
}
default: {
throw new IllegalArgumentException("unkown uri " + uri);
}
}
Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
if (cursor != null) {
cursor.setNotificationUri(getContext().getContentResolver(), TagContract.AUTHORITY_URI);
}
return cursor;
}
@Override
protected Uri insertInTransaction(Uri uri, ContentValues values) {
SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
int match = MATCHER.match(uri);
long id = -1;
switch (match) {
case NDEF_MESSAGES: {
id = db.insert(TagDBHelper.TABLE_NAME_NDEF_MESSAGES, NdefMessages.TITLE, values);
break;
}
default: {
throw new IllegalArgumentException("unkown uri " + uri);
}
}
if (id >= 0) {
return ContentUris.withAppendedId(uri, id);
}
return null;
}
@Override
protected int updateInTransaction(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
int match = MATCHER.match(uri);
int count = 0;
switch (match) {
case NDEF_MESSAGES_ID: {
selection = concatenateWhere(selection,
TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
}
case NDEF_MESSAGES: {
count = db.update(TagDBHelper.TABLE_NAME_NDEF_MESSAGES, values, selection,
selectionArgs);
break;
}
default: {
throw new IllegalArgumentException("unkown uri " + uri);
}
}
return count;
}
@Override
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
int match = MATCHER.match(uri);
int count = 0;
switch (match) {
case NDEF_MESSAGES_ID: {
selection = concatenateWhere(selection,
TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
}
case NDEF_MESSAGES: {
count = db.delete(TagDBHelper.TABLE_NAME_NDEF_MESSAGES, selection, selectionArgs);
break;
}
default: {
throw new IllegalArgumentException("unkown uri " + uri);
}
}
return count;
}
private NdefRecord getRecord(Uri uri) {
NdefRecord record = null;
Cursor cursor = null;
try {
SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
cursor = db.query(TagDBHelper.TABLE_NAME_NDEF_MESSAGES,
new String[] { NdefMessages.BYTES }, "_id=?",
new String[] { uri.getPathSegments().get(1) }, null, null, null, null);
if (cursor.moveToFirst()) {
NdefMessage msg = new NdefMessage(cursor.getBlob(0));
NdefRecord[] records = msg.getRecords();
int offset = Integer.parseInt(uri.getPathSegments().get(2));
if (records != null && offset < records.length) {
record = records[offset];
}
}
} catch (FormatException e) {
Log.e(TAG, "Invalid NdefMessage format", e);
} finally {
if (cursor != null) cursor.close();
}
return record;
}
@Override
public String getType(Uri uri) {
int match = MATCHER.match(uri);
switch (match) {
case NDEF_MESSAGES_ID: {
return NdefMessages.CONTENT_ITEM_TYPE;
}
case NDEF_MESSAGES: {
return NdefMessages.CONTENT_TYPE;
}
case NDEF_MESSAGES_ID_MIME: {
NdefRecord record = getRecord(uri);
if (record != null) {
return new String(record.getType(), Charsets.US_ASCII).toLowerCase();
}
return null;
}
default: {
throw new IllegalArgumentException("unknown uri " + uri);
}
}
}
@Override
protected void notifyChange() {
getContext().getContentResolver().notifyChange(TagContract.AUTHORITY_URI, null, false);
}
@Override
public void writeMimeDataToPipe(ParcelFileDescriptor output, Uri uri) {
NdefRecord record = getRecord(uri);
if (record == null) return;
try {
byte[] data = record.getPayload();
FileOutputStream os = new FileOutputStream(output.getFileDescriptor());
os.write(data);
os.flush();
} catch (IOException e) {
Log.e(TAG, "failed to write MIME data to " + uri, e);
}
}
/**
* A helper function for implementing {@link #openFile}, for
* creating a data pipe and background thread allowing you to stream
* generated data back to the client. This function returns a new
* ParcelFileDescriptor that should be returned to the caller (the caller
* is responsible for closing it).
*
* @param uri The URI whose data is to be written.
* @param func Interface implementing the function that will actually
* stream the data.
* @return Returns a new ParcelFileDescriptor holding the read side of
* the pipe. This should be returned to the caller for reading; the caller
* is responsible for closing it when done.
*/
public ParcelFileDescriptor openMimePipe(final Uri uri,
final TagProviderPipeDataWriter func) throws FileNotFoundException {
try {
final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
@Override
protected Object doInBackground(Object... params) {
func.writeMimeDataToPipe(fds[1], uri);
try {
fds[1].close();
} catch (IOException e) {
Log.w(TAG, "Failure closing pipe", e);
}
return null;
}
};
task.execute((Object[])null);
return fds[0];
} catch (IOException e) {
throw new FileNotFoundException("failure making pipe");
}
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Preconditions.checkArgument("r".equals(mode));
Preconditions.checkArgument(MATCHER.match(uri) == NDEF_MESSAGES_ID_MIME);
return openMimePipe(uri, this);
}
}