package com.firewings.smstools;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteTransactionListener;
import android.net.Uri;
import android.os.Binder;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.SyncStateContract;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.util.Log;
public class SmsProvider extends ContentProvider implements SQLiteTransactionListener {
private static final String TAG = "SmsProvider";
private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
private DbHelper mDbHelper;
private SQLiteDatabase mDb;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int SMS = 1000;
private static final int SMS_ID = 1001;
public static final String SMS_ITEM_TYPE = "vnd.android.cursor.item/sms";
public static final String AUTHORITY = "com.firewings.smstools";
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
public static final HashMap<string, string=""> sCountProjectionMap;
public static final HashMap<string, string=""> smsProjectionMap;
static {
final UriMatcher matcher = sUriMatcher;
matcher.addURI(AUTHORITY, "sms", SMS);
matcher.addURI(AUTHORITY, "sms/#", SMS_ID);
sCountProjectionMap = new LinkedHashMap<string, string="">();
sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
smsProjectionMap = new LinkedHashMap<string, string="">();
smsProjectionMap.put(DbHelper.SmsColumns._ID, DbHelper.SmsColumns._ID);
smsProjectionMap.put(DbHelper.SmsColumns.ADDRESS, DbHelper.SmsColumns.ADDRESS);
smsProjectionMap.put(DbHelper.SmsColumns.PERSON, DbHelper.SmsColumns.PERSON);
smsProjectionMap.put(DbHelper.SmsColumns.DATE, DbHelper.SmsColumns.DATE);
smsProjectionMap.put(DbHelper.SmsColumns.TYPE, DbHelper.SmsColumns.TYPE);
smsProjectionMap.put(DbHelper.SmsColumns.BODY, DbHelper.SmsColumns.BODY);
smsProjectionMap.put(DbHelper.SmsColumns.SEND, DbHelper.SmsColumns.SEND);
}
private final ThreadLocal<boolean> mApplyingBatch = new ThreadLocal<boolean>();
private volatile boolean mNotifyChange;
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
boolean applyingBatch = applyingBatch();
if (!applyingBatch) {
mDb = mDbHelper.getWritableDatabase();
mDb.beginTransactionWithListener(this);
try {
count = deleteInTransaction(uri, selection, selectionArgs);
if (count > 0) {
mNotifyChange = true;
}
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
onEndTransaction();
} else {
count = deleteInTransaction(uri, selection, selectionArgs);
if (count > 0) {
mNotifyChange = true;
}
}
return count;
}
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "updateInTransaction: " + uri);
}
int count = 0;
final int match = sUriMatcher.match(uri);
switch (match) {
case SMS: {
count = mDb.delete(DbHelper.Tables.SMS, selection, selectionArgs);
break;
}
case SMS_ID: {
long smsId = ContentUris.parseId(uri);
if (selection != null) {
selectionArgs = selectionArg(selectionArgs, String.valueOf(smsId));
count = mDb.delete(DbHelper.Tables.SMS, RawContacts._ID + "=?" + " AND (" + selection + ")", selectionArgs);
} else {
count = mDb.delete(DbHelper.Tables.SMS, RawContacts._ID + "=?", new String[] { String.valueOf(smsId) });
}
break;
}
default: {
throw new UnsupportedOperationException(exceptionMessage(uri));
}
}
return count;
}
@Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case SMS:
return SMS_ITEM_TYPE;
default:
throw new IllegalArgumentException(exceptionMessage(uri));
}
}
/**
* Returns a detailed exception message for the supplied URI. It includes
* the calling user and calling package(s).
*/
public String exceptionMessage(Uri uri) {
return exceptionMessage(null, uri);
}
/**
* Returns a detailed exception message for the supplied URI. It includes
* the calling user and calling package(s).
*/
public String exceptionMessage(String message, Uri uri) {
StringBuilder sb = new StringBuilder();
if (message != null) {
sb.append(message).append("; ");
}
sb.append("URI: ").append(uri);
final PackageManager pm = getContext().getPackageManager();
int callingUid = Binder.getCallingUid();
sb.append(", calling user: ");
String userName = pm.getNameForUid(callingUid);
if (userName != null) {
sb.append(userName);
} else {
sb.append(callingUid);
}
final String[] callerPackages = pm.getPackagesForUid(callingUid);
if (callerPackages != null && callerPackages.length > 0) {
if (callerPackages.length == 1) {
sb.append(", calling package:");
sb.append(callerPackages[0]);
} else {
sb.append(", calling package is one of: [");
for (int i = 0; i < callerPackages.length; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append(callerPackages[i]);
}
sb.append("]");
}
}
return sb.toString();
}
private boolean applyingBatch() {
return mApplyingBatch.get() != null && mApplyingBatch.get();
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri result = null;
boolean applyingBatch = applyingBatch();
if (!applyingBatch) {
mDb = mDbHelper.getWritableDatabase();
mDb.beginTransactionWithListener(this);
try {
result = insertInTransaction(uri, values);
if (result != null) {
mNotifyChange = true;
}
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
onEndTransaction();
} else {
result = insertInTransaction(uri, values);
if (result != null) {
mNotifyChange = true;
}
}
return result;
}
protected Uri insertInTransaction(Uri uri, ContentValues values) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "insertInTransaction: " + uri + " " + values);
}
final int match = sUriMatcher.match(uri);
long id = 0;
switch (match) {
case SMS: {
values.putNull(DbHelper.SmsColumns._ID);
id = mDb.insert(DbHelper.Tables.SMS, DbHelper.SmsColumns._ID, values);
break;
}
default: {
throw new UnsupportedOperationException(exceptionMessage(uri));
}
}
if (id < 0) {
return null;
}
return ContentUris.withAppendedId(uri, id);
}
@Override
public boolean onCreate() {
try {
return initialize();
} catch (RuntimeException e) {
Log.e(TAG, "Cannot start provider", e);
return false;
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "query: " + uri);
}
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String groupBy = null;
String limit = getLimit(uri);
final int match = sUriMatcher.match(uri);
switch (match) {
case SMS: {
setTablesAndProjectionMapForSms(qb, uri);
break;
}
}
return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
}
private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection, String selection, String[] selectionArgs, String sortOrder, String groupBy, String limit) {
if (projection != null && projection.length == 1 && BaseColumns._COUNT.equals(projection[0])) {
qb.setProjectionMap(sCountProjectionMap);
}
final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), AUTHORITY_URI);
}
return c;
}
private void setTablesAndProjectionMapForSms(SQLiteQueryBuilder qb, Uri uri) {
StringBuilder sb = new StringBuilder();
sb.append(DbHelper.Tables.SMS);
qb.setTables(sb.toString());
qb.setProjectionMap(smsProjectionMap);
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
boolean applyingBatch = applyingBatch();
if (!applyingBatch) {
mDb = mDbHelper.getWritableDatabase();
mDb.beginTransactionWithListener(this);
try {
count = updateInTransaction(uri, values, selection, selectionArgs);
if (count > 0) {
mNotifyChange = true;
}
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
onEndTransaction();
} else {
count = updateInTransaction(uri, values, selection, selectionArgs);
if (count > 0) {
mNotifyChange = true;
}
}
return count;
}
protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "updateInTransaction: " + uri);
}
int count = 0;
final int match = sUriMatcher.match(uri);
switch (match) {
case SMS: {
count = mDb.update(DbHelper.Tables.SMS, values, selection, selectionArgs);
break;
}
case SMS_ID: {
long smsId = ContentUris.parseId(uri);
if (selection != null) {
selectionArgs = selectionArg(selectionArgs, String.valueOf(smsId));
count = mDb.update(DbHelper.Tables.SMS, values, RawContacts._ID + "=?" + " AND (" + selection + ")", selectionArgs);
} else {
count = mDb.update(DbHelper.Tables.SMS, values, RawContacts._ID + "=?", new String[] { String.valueOf(smsId) });
}
break;
}
default: {
throw new UnsupportedOperationException(exceptionMessage(uri));
}
}
return count;
}
/**
* Inserts an argument at the beginning of the selection arg list.
*/
private String[] selectionArg(String[] selectionArgs, String arg) {
if (selectionArgs == null) {
return new String[] { arg };
} else {
int newLength = selectionArgs.length + 1;
String[] newSelectionArgs = new String[newLength];
newSelectionArgs[0] = arg;
System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
return newSelectionArgs;
}
}
private boolean initialize() {
final Context context = getContext();
mDbHelper = (DbHelper) DbHelper.getInstance(context);
return true;
}
private String getLimit(Uri uri) {
String limitParam = getQueryParameter(uri, "limit");
if (limitParam == null) {
return null;
}
// make sure that the limit is a non-negative integer
try {
int l = Integer.parseInt(limitParam);
if (l < 0) {
Log.w(TAG, "Invalid limit parameter: " + limitParam);
return null;
}
return String.valueOf(l);
} catch (NumberFormatException ex) {
Log.w(TAG, "Invalid limit parameter: " + limitParam);
return null;
}
}
/* package */static String getQueryParameter(Uri uri, String parameter) {
String query = uri.getEncodedQuery();
if (query == null) {
return null;
}
int queryLength = query.length();
int parameterLength = parameter.length();
String value;
int index = 0;
while (true) {
index = query.indexOf(parameter, index);
if (index == -1) {
return null;
}
index += parameterLength;
if (queryLength == index) {
return null;
}
if (query.charAt(index) == '=') {
index++;
break;
}
}
int ampIndex = query.indexOf('&', index);
if (ampIndex == -1) {
value = query.substring(index);
} else {
value = query.substring(index, ampIndex);
}
return Uri.decode(value);
}
public void onBegin() {
// TODO Auto-generated method stub
}
public void onCommit() {
// TODO Auto-generated method stub
}
public void onRollback() {
// TODO Auto-generated method stub
}
protected void onEndTransaction() {
if (mNotifyChange) {
mNotifyChange = false;
notifyChange(true);
}
}
protected void notifyChange(boolean syncToNetwork) {
getContext().getContentResolver().notifyChange(AUTHORITY_URI, null, syncToNetwork);
}
}