/**
* Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
* This file is part of CSipSimple.
*
* CSipSimple is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* If you own a pjsip commercial license you can also redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as an android library.
*
* CSipSimple is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CSipSimple. If not, see <http://www.gnu.org/licenses/>.
*/
package com.csipsimple.db;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.provider.CallLog;
import android.support.v4.database.DatabaseUtilsCompat;
import android.text.TextUtils;
import com.csipsimple.api.SipManager;
import com.csipsimple.api.SipMessage;
import com.csipsimple.api.SipProfile;
import com.csipsimple.api.SipProfileState;
import com.csipsimple.db.DBAdapter.DatabaseHelper;
import com.csipsimple.models.Filter;
import com.csipsimple.utils.Log;
import com.csipsimple.utils.backup.BackupWrapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class DBProvider extends ContentProvider {
private DatabaseHelper mOpenHelper;
private static final String UNKNOWN_URI_LOG = "Unknown URI ";
// Ids for matcher
private static final int ACCOUNTS = 1, ACCOUNTS_ID = 2;
private static final int ACCOUNTS_STATUS = 3, ACCOUNTS_STATUS_ID = 4;
private static final int CALLLOGS = 5, CALLLOGS_ID = 6;
private static final int FILTERS = 7, FILTERS_ID = 8;
private static final int MESSAGES = 9, MESSAGES_ID = 10;
private static final int THREADS = 11, THREADS_ID = 12;
/**
* A UriMatcher instance
*/
private static final UriMatcher URI_MATCHER;
static {
// Create and initialize URI matcher.
URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipProfile.ACCOUNTS_TABLE_NAME, ACCOUNTS);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipProfile.ACCOUNTS_TABLE_NAME + "/#", ACCOUNTS_ID);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipProfile.ACCOUNTS_STATUS_TABLE_NAME, ACCOUNTS_STATUS);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipProfile.ACCOUNTS_STATUS_TABLE_NAME + "/#", ACCOUNTS_STATUS_ID);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipManager.CALLLOGS_TABLE_NAME, CALLLOGS);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipManager.CALLLOGS_TABLE_NAME + "/#", CALLLOGS_ID);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipManager.FILTERS_TABLE_NAME, FILTERS);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipManager.FILTERS_TABLE_NAME + "/#", FILTERS_ID);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipMessage.MESSAGES_TABLE_NAME, MESSAGES);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipMessage.MESSAGES_TABLE_NAME + "/#", MESSAGES_ID);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipMessage.THREAD_ALIAS, THREADS);
URI_MATCHER.addURI(SipManager.AUTHORITY, SipMessage.THREAD_ALIAS + "/*", THREADS_ID);
}
public final static String[] ACCOUNT_FULL_PROJECTION = {
SipProfile.FIELD_ID,
// Application relative fields
SipProfile.FIELD_ACTIVE, SipProfile.FIELD_WIZARD, SipProfile.FIELD_DISPLAY_NAME,
// Custom datas
SipProfile.FIELD_WIZARD_DATA,
// Here comes pjsua_acc_config fields
SipProfile.FIELD_PRIORITY, SipProfile.FIELD_ACC_ID, SipProfile.FIELD_REG_URI,
SipProfile.FIELD_MWI_ENABLED, SipProfile.FIELD_PUBLISH_ENABLED, SipProfile.FIELD_REG_TIMEOUT, SipProfile.FIELD_KA_INTERVAL,
SipProfile.FIELD_PIDF_TUPLE_ID,
SipProfile.FIELD_FORCE_CONTACT, SipProfile.FIELD_ALLOW_CONTACT_REWRITE, SipProfile.FIELD_CONTACT_REWRITE_METHOD,
SipProfile.FIELD_ALLOW_VIA_REWRITE, SipProfile.FIELD_ALLOW_SDP_NAT_REWRITE,
SipProfile.FIELD_CONTACT_PARAMS, SipProfile.FIELD_CONTACT_URI_PARAMS,
SipProfile.FIELD_TRANSPORT, SipProfile.FIELD_DEFAULT_URI_SCHEME, SipProfile.FIELD_USE_SRTP, SipProfile.FIELD_USE_ZRTP,
SipProfile.FIELD_REG_DELAY_BEFORE_REFRESH,
// RTP config
SipProfile.FIELD_RTP_PORT, SipProfile.FIELD_RTP_PUBLIC_ADDR, SipProfile.FIELD_RTP_BOUND_ADDR,
SipProfile.FIELD_RTP_ENABLE_QOS, SipProfile.FIELD_RTP_QOS_DSCP,
// Proxy infos
SipProfile.FIELD_PROXY, SipProfile.FIELD_REG_USE_PROXY,
// And now cred_info since for now only one cred info can be managed
// In future release a credential table should be created
SipProfile.FIELD_REALM, SipProfile.FIELD_SCHEME, SipProfile.FIELD_USERNAME, SipProfile.FIELD_DATATYPE,
SipProfile.FIELD_DATA,
SipProfile.FIELD_AUTH_INITIAL_AUTH, SipProfile.FIELD_AUTH_ALGO,
// CSipSimple specific
SipProfile.FIELD_SIP_STACK, SipProfile.FIELD_VOICE_MAIL_NBR,
SipProfile.FIELD_TRY_CLEAN_REGISTERS, SipProfile.FIELD_ANDROID_GROUP,
// RFC 5626
SipProfile.FIELD_USE_RFC5626, SipProfile.FIELD_RFC5626_INSTANCE_ID, SipProfile.FIELD_RFC5626_REG_ID,
// Video
SipProfile.FIELD_VID_IN_AUTO_SHOW, SipProfile.FIELD_VID_OUT_AUTO_TRANSMIT,
// STUN, ICE, TURN
SipProfile.FIELD_SIP_STUN_USE, SipProfile.FIELD_MEDIA_STUN_USE,
SipProfile.FIELD_ICE_CFG_USE, SipProfile.FIELD_ICE_CFG_ENABLE,
SipProfile.FIELD_TURN_CFG_USE, SipProfile.FIELD_TURN_CFG_ENABLE, SipProfile.FIELD_TURN_CFG_SERVER, SipProfile.FIELD_TURN_CFG_USER, SipProfile.FIELD_TURN_CFG_PASSWORD,
SipProfile.FIELD_IPV6_MEDIA_USE,
};
public final static Class<?>[] ACCOUNT_FULL_PROJECTION_TYPES = {
Long.class,
Integer.class, String.class, String.class,
String.class,
Integer.class, String.class, String.class,
Boolean.class, Integer.class, Integer.class, Integer.class,
String.class,
String.class, Integer.class, Integer.class,
Integer.class, Integer.class,
String.class, String.class,
Integer.class, String.class, Integer.class, Integer.class,
Integer.class,
// RTP config
Integer.class, String.class, String.class,
Integer.class, Integer.class,
// Proxy infos
String.class, Integer.class,
// Credentials
String.class, String.class, String.class, Integer.class,
String.class,
Boolean.class, String.class,
// CSipSimple specific
Integer.class, String.class,
Integer.class, String.class,
// RFC 5626
Integer.class, String.class, String.class,
// Video
Integer.class, Integer.class,
// STUN, ICE, TURN
Integer.class, Integer.class,
Integer.class, Integer.class,
Integer.class, Integer.class, String.class, String.class, String.class,
// IPV6
Integer.class
};
private static final String[] CALL_LOG_FULL_PROJECTION = new String[] {
CallLog.Calls._ID,
CallLog.Calls.CACHED_NAME,
CallLog.Calls.CACHED_NUMBER_LABEL,
CallLog.Calls.CACHED_NUMBER_TYPE,
CallLog.Calls.DATE,
CallLog.Calls.DURATION,
CallLog.Calls.NEW,
CallLog.Calls.NUMBER,
CallLog.Calls.TYPE,
SipManager.CALLLOG_PROFILE_ID_FIELD,
SipManager.CALLLOG_STATUS_CODE_FIELD,
SipManager.CALLLOG_STATUS_TEXT_FIELD
};
private static final String[] MESSAGES_FULL_PROJECTION = new String[] {
SipMessage.FIELD_ID,
SipMessage.FIELD_FROM,
SipMessage.FIELD_TO,
SipMessage.FIELD_CONTACT,
SipMessage.FIELD_BODY,
SipMessage.FIELD_MIME_TYPE,
SipMessage.FIELD_TYPE,
SipMessage.FIELD_DATE,
SipMessage.FIELD_STATUS,
SipMessage.FIELD_READ,
SipMessage.FIELD_FROM_FULL,
};
private static final String[] FILTERS_FULL_PROJECTION = new String[] {
Filter._ID,
Filter.FIELD_PRIORITY,
Filter.FIELD_ACCOUNT,
Filter.FIELD_MATCHES,
Filter.FIELD_REPLACE,
Filter.FIELD_ACTION,
};
private static final String THIS_FILE = "DBProvider";
// Map active account id (id for sql settings database) with SipProfileState that contains stack id and other status infos
private final Map<Long, ContentValues> profilesStatus = new HashMap<Long, ContentValues>();
@Override
public String getType(Uri uri) {
switch (URI_MATCHER.match(uri)) {
case ACCOUNTS:
return SipProfile.ACCOUNT_CONTENT_TYPE;
case ACCOUNTS_ID:
return SipProfile.ACCOUNT_CONTENT_ITEM_TYPE;
case ACCOUNTS_STATUS:
return SipProfile.ACCOUNT_STATUS_CONTENT_TYPE;
case ACCOUNTS_STATUS_ID:
return SipProfile.ACCOUNT_STATUS_CONTENT_ITEM_TYPE;
case CALLLOGS :
return SipManager.CALLLOG_CONTENT_TYPE;
case CALLLOGS_ID :
return SipManager.CALLLOG_CONTENT_ITEM_TYPE;
case FILTERS:
return SipManager.FILTER_CONTENT_TYPE;
case FILTERS_ID:
return SipManager.FILTER_CONTENT_ITEM_TYPE;
case MESSAGES:
return SipMessage.MESSAGE_CONTENT_TYPE;
case MESSAGES_ID:
return SipMessage.MESSAGE_CONTENT_ITEM_TYPE;
case THREADS:
return SipMessage.MESSAGE_CONTENT_TYPE;
case THREADS_ID:
return SipMessage.MESSAGE_CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
}
}
@Override
public boolean onCreate() {
mOpenHelper = new DatabaseHelper(getContext());
// Assumes that any failures will be reported by a thrown exception.
return true;
}
private static final String MESSAGES_THREAD_SELECTION = "("+ SipMessage.FIELD_FROM+"=? AND "+
SipMessage.FIELD_TYPE+" IN ("+
Integer.toString(SipMessage.MESSAGE_TYPE_INBOX)
+") )"
+ " OR " +
"("+ SipMessage.FIELD_TO+"=? AND "+
SipMessage.FIELD_TYPE+" IN ("+
Integer.toString(SipMessage.MESSAGE_TYPE_QUEUED)+", "+
Integer.toString(SipMessage.MESSAGE_TYPE_FAILED)+", "+
Integer.toString(SipMessage.MESSAGE_TYPE_SENT)
+") )";
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String finalWhere;
int count = 0;
int matched = URI_MATCHER.match(uri);
Uri regUri = uri;
List<String> possibles = getPossibleFieldsForType(matched);
checkSelection(possibles, where);
ArrayList<Long> oldRegistrationsAccounts = null;
switch (matched) {
case ACCOUNTS:
count = db.delete(SipProfile.ACCOUNTS_TABLE_NAME, where, whereArgs);
break;
case ACCOUNTS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(SipProfile.FIELD_ID + " = " + ContentUris.parseId(uri), where);
count = db.delete(SipProfile.ACCOUNTS_TABLE_NAME, finalWhere, whereArgs);
break;
case CALLLOGS:
count = db.delete(SipManager.CALLLOGS_TABLE_NAME, where, whereArgs);
break;
case CALLLOGS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(CallLog.Calls._ID + " = " + ContentUris.parseId(uri), where);
count = db.delete(SipManager.CALLLOGS_TABLE_NAME, finalWhere, whereArgs);
break;
case FILTERS:
count = db.delete(SipManager.FILTERS_TABLE_NAME, where, whereArgs);
break;
case FILTERS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(Filter._ID + " = " + ContentUris.parseId(uri), where);
count = db.delete(SipManager.FILTERS_TABLE_NAME, finalWhere, whereArgs);
break;
case MESSAGES:
count = db.delete(SipMessage.MESSAGES_TABLE_NAME, where, whereArgs);
break;
case MESSAGES_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(SipMessage.FIELD_ID + " = " + ContentUris.parseId(uri), where);
count = db.delete(SipMessage.MESSAGES_TABLE_NAME, finalWhere, whereArgs);
break;
case THREADS_ID:
String from = uri.getLastPathSegment();
if(!TextUtils.isEmpty(from)) {
count = db.delete(SipMessage.MESSAGES_TABLE_NAME, MESSAGES_THREAD_SELECTION, new String[] {
from, from
});
}else {
count = 0;
}
regUri = SipMessage.MESSAGE_URI;
break;
case ACCOUNTS_STATUS:
oldRegistrationsAccounts = new ArrayList<Long>();
synchronized (profilesStatus) {
for(Long accId : profilesStatus.keySet()) {
oldRegistrationsAccounts.add(accId);
}
profilesStatus.clear();
}
break;
case ACCOUNTS_STATUS_ID:
long id = ContentUris.parseId(uri);
synchronized (profilesStatus) {
profilesStatus.remove(id);
}
break;
default:
throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
}
getContext().getContentResolver().notifyChange(regUri, null);
if(matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {
long rowId = ContentUris.parseId(uri);
if(rowId >= 0) {
if(matched == ACCOUNTS_ID) {
broadcastAccountDelete(rowId);
}else if(matched == ACCOUNTS_STATUS_ID) {
broadcastRegistrationChange(rowId);
}
}
}
if (matched == FILTERS || matched == FILTERS_ID) {
Filter.resetCache();
}
if(matched == ACCOUNTS_STATUS && oldRegistrationsAccounts != null) {
for(Long accId : oldRegistrationsAccounts) {
if(accId != null) {
broadcastRegistrationChange(accId);
}
}
}
return count;
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
int matched = URI_MATCHER.match(uri);
String matchedTable = null;
Uri baseInsertedUri = null;
switch (matched) {
case ACCOUNTS:
case ACCOUNTS_ID:
matchedTable = SipProfile.ACCOUNTS_TABLE_NAME;
baseInsertedUri = SipProfile.ACCOUNT_ID_URI_BASE;
break;
case CALLLOGS:
case CALLLOGS_ID:
matchedTable = SipManager.CALLLOGS_TABLE_NAME;
baseInsertedUri = SipManager.CALLLOG_ID_URI_BASE;
break;
case FILTERS:
case FILTERS_ID:
matchedTable = SipManager.FILTERS_TABLE_NAME;
baseInsertedUri = SipManager.FILTER_ID_URI_BASE;
break;
case MESSAGES:
case MESSAGES_ID:
matchedTable = SipMessage.MESSAGES_TABLE_NAME;
baseInsertedUri = SipMessage.MESSAGE_ID_URI_BASE;
break;
case ACCOUNTS_STATUS_ID:
long id = ContentUris.parseId(uri);
synchronized (profilesStatus){
SipProfileState ps = new SipProfileState();
if(profilesStatus.containsKey(id)) {
ContentValues currentValues = profilesStatus.get(id);
ps.createFromContentValue(currentValues);
}
ps.createFromContentValue(initialValues);
ContentValues cv = ps.getAsContentValue();
cv.put(SipProfileState.ACCOUNT_ID, id);
profilesStatus.put(id, cv);
Log.d(THIS_FILE, "Added "+cv);
}
getContext().getContentResolver().notifyChange(uri, null);
return uri;
default:
break;
}
if ( matchedTable == null ) {
throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
}
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(matchedTable, null, values);
// If the insert succeeded, the row ID exists.
if (rowId >= 0) {
// TODO : for inserted account register it here
Uri retUri = ContentUris.withAppendedId(baseInsertedUri, rowId);
getContext().getContentResolver().notifyChange(retUri, null);
if(matched == ACCOUNTS || matched == ACCOUNTS_ID) {
broadcastAccountChange(rowId);
}
if(matched == CALLLOGS || matched == CALLLOGS_ID) {
db.delete(SipManager.CALLLOGS_TABLE_NAME, CallLog.Calls._ID + " IN " +
"(SELECT "+CallLog.Calls._ID+" FROM "+SipManager.CALLLOGS_TABLE_NAME+" ORDER BY " +
CallLog.Calls.DEFAULT_SORT_ORDER + " LIMIT -1 OFFSET 500)", null);
}
if(matched == ACCOUNTS_STATUS || matched == ACCOUNTS_STATUS_ID) {
broadcastRegistrationChange(rowId);
}
if (matched == FILTERS || matched == FILTERS_ID) {
Filter.resetCache();
}
return retUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Constructs a new query builder and sets its table name
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String finalSortOrder = sortOrder;
String[] finalSelectionArgs = selectionArgs;
String finalGrouping = null;
String finalHaving = null;
int type = URI_MATCHER.match(uri);
Uri regUri = uri;
// Security check to avoid data retrieval from outside
int remoteUid = Binder.getCallingUid();
int selfUid = android.os.Process.myUid();
if(remoteUid != selfUid) {
if (type == ACCOUNTS || type == ACCOUNTS_ID) {
for(String proj : projection) {
if(proj.toLowerCase().contains(SipProfile.FIELD_DATA) || proj.toLowerCase().contains("*")) {
throw new SecurityException("Password not readable from external apps");
}
}
}
}
// Security check to avoid project of invalid fields or lazy projection
List<String> possibles = getPossibleFieldsForType(type);
if(possibles == null) {
throw new SecurityException("You are asking wrong values " + type);
}
checkProjection(possibles, projection);
checkSelection(possibles, selection);
Cursor c;
long id;
switch (type) {
case ACCOUNTS:
qb.setTables(SipProfile.ACCOUNTS_TABLE_NAME);
if(sortOrder == null) {
finalSortOrder = SipProfile.FIELD_PRIORITY + " ASC";
}
break;
case ACCOUNTS_ID:
qb.setTables(SipProfile.ACCOUNTS_TABLE_NAME);
qb.appendWhere(SipProfile.FIELD_ID + "=?");
finalSelectionArgs = DatabaseUtilsCompat.appendSelectionArgs(selectionArgs, new String[] { uri.getLastPathSegment() });
break;
case CALLLOGS:
qb.setTables(SipManager.CALLLOGS_TABLE_NAME);
if(sortOrder == null) {
finalSortOrder = CallLog.Calls.DATE + " DESC";
}
break;
case CALLLOGS_ID:
qb.setTables(SipManager.CALLLOGS_TABLE_NAME);
qb.appendWhere(CallLog.Calls._ID + "=?");
finalSelectionArgs = DatabaseUtilsCompat.appendSelectionArgs(selectionArgs, new String[] { uri.getLastPathSegment() });
break;
case FILTERS:
qb.setTables(SipManager.FILTERS_TABLE_NAME);
if(sortOrder == null) {
finalSortOrder = Filter.DEFAULT_ORDER;
}
break;
case FILTERS_ID:
qb.setTables(SipManager.FILTERS_TABLE_NAME);
qb.appendWhere(Filter._ID + "=?");
finalSelectionArgs = DatabaseUtilsCompat.appendSelectionArgs(selectionArgs, new String[] { uri.getLastPathSegment() });
break;
case MESSAGES:
qb.setTables(SipMessage.MESSAGES_TABLE_NAME);
if(sortOrder == null) {
finalSortOrder = SipMessage.FIELD_DATE + " DESC";
}
break;
case MESSAGES_ID:
qb.setTables(SipMessage.MESSAGES_TABLE_NAME);
qb.appendWhere(SipMessage.FIELD_ID + "=?");
finalSelectionArgs = DatabaseUtilsCompat.appendSelectionArgs(selectionArgs, new String[] { uri.getLastPathSegment() });
break;
case THREADS:
qb.setTables(SipMessage.MESSAGES_TABLE_NAME);
if(sortOrder == null) {
finalSortOrder = SipMessage.FIELD_DATE + " DESC";
}
projection = new String[]{
"ROWID AS _id",
SipMessage.FIELD_FROM,
SipMessage.FIELD_FROM_FULL,
SipMessage.FIELD_TO,
"CASE " +
"WHEN " + SipMessage.FIELD_FROM + "='SELF' THEN "
+ SipMessage.FIELD_TO +
" WHEN " + SipMessage.FIELD_FROM + "!='SELF' THEN "
+ SipMessage.FIELD_FROM
+ " END AS message_ordering",
SipMessage.FIELD_BODY,
"MAX(" + SipMessage.FIELD_DATE + ") AS " + SipMessage.FIELD_DATE,
"MIN(" + SipMessage.FIELD_READ + ") AS " + SipMessage.FIELD_READ,
//SipMessage.FIELD_READ,
"COUNT(" + SipMessage.FIELD_DATE + ") AS counter"
};
//qb.appendWhere(SipMessage.FIELD_TYPE + " in (" + SipMessage.MESSAGE_TYPE_INBOX
// + "," + SipMessage.MESSAGE_TYPE_SENT + ")");
finalGrouping = "message_ordering";
regUri = SipMessage.MESSAGE_URI;
break;
case THREADS_ID:
qb.setTables(SipMessage.MESSAGES_TABLE_NAME);
if(sortOrder == null) {
finalSortOrder = SipMessage.FIELD_DATE + " DESC";
}
projection = new String[]{
"ROWID AS _id",
SipMessage.FIELD_FROM,
SipMessage.FIELD_TO,
SipMessage.FIELD_BODY,
SipMessage.FIELD_DATE,
SipMessage.FIELD_MIME_TYPE,
SipMessage.FIELD_TYPE,
SipMessage.FIELD_STATUS,
SipMessage.FIELD_FROM_FULL
};
qb.appendWhere(MESSAGES_THREAD_SELECTION);
String from = uri.getLastPathSegment();
finalSelectionArgs = DatabaseUtilsCompat.appendSelectionArgs(selectionArgs, new String[] { from, from });
regUri = SipMessage.MESSAGE_URI;
break;
case ACCOUNTS_STATUS:
synchronized (profilesStatus) {
ContentValues[] cvs = new ContentValues[profilesStatus.size()];
int i = 0;
for(ContentValues ps : profilesStatus.values()) {
cvs[i] = ps;
i++;
}
c = getCursor(cvs);
}
if(c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
case ACCOUNTS_STATUS_ID:
id = ContentUris.parseId(uri);
synchronized (profilesStatus) {
ContentValues cv = profilesStatus.get(id);
if(cv == null) {
return null;
}
c = getCursor(new ContentValues[] {cv});
}
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
default:
throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
c = qb.query(db, projection, selection, finalSelectionArgs,
finalGrouping, finalHaving, finalSortOrder);
c.setNotificationUri(getContext().getContentResolver(), regUri);
return c;
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
String finalWhere;
int matched = URI_MATCHER.match(uri);
List<String> possibles = getPossibleFieldsForType(matched);
checkSelection(possibles, where);
switch (matched) {
case ACCOUNTS:
count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, where, whereArgs);
break;
case ACCOUNTS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(SipProfile.FIELD_ID + " = " + ContentUris.parseId(uri), where);
count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, finalWhere, whereArgs);
break;
case CALLLOGS:
count = db.update(SipManager.CALLLOGS_TABLE_NAME, values, where, whereArgs);
break;
case CALLLOGS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(CallLog.Calls._ID + " = " + ContentUris.parseId(uri), where);
count = db.update(SipManager.CALLLOGS_TABLE_NAME, values, finalWhere, whereArgs);
break;
case FILTERS:
count = db.update(SipManager.FILTERS_TABLE_NAME, values, where, whereArgs);
break;
case FILTERS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(Filter._ID + " = " + ContentUris.parseId(uri), where);
count = db.update(SipManager.FILTERS_TABLE_NAME, values, finalWhere, whereArgs);
break;
case MESSAGES:
count = db.update(SipMessage.MESSAGES_TABLE_NAME, values, where, whereArgs);
break;
case MESSAGES_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(SipMessage.FIELD_ID + " = " + ContentUris.parseId(uri), where);
count = db.update(SipMessage.MESSAGES_TABLE_NAME, values, where, whereArgs);
break;
case ACCOUNTS_STATUS_ID:
long id = ContentUris.parseId(uri);
synchronized (profilesStatus){
SipProfileState ps = new SipProfileState();
if(profilesStatus.containsKey(id)) {
ContentValues currentValues = profilesStatus.get(id);
ps.createFromContentValue(currentValues);
}
ps.createFromContentValue(values);
ContentValues cv = ps.getAsContentValue();
cv.put(SipProfileState.ACCOUNT_ID, id);
profilesStatus.put(id, cv);
Log.d(THIS_FILE, "Updated "+cv);
}
count = 1;
break;
default:
throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
long rowId = -1;
if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {
rowId = ContentUris.parseId(uri);
}
if (rowId >= 0) {
if (matched == ACCOUNTS_ID) {
// Don't broadcast if we only changed wizard or only changed priority
boolean doBroadcast = true;
if(values.size() == 1) {
if(values.containsKey(SipProfile.FIELD_WIZARD)) {
doBroadcast = false;
}else if(values.containsKey(SipProfile.FIELD_PRIORITY)) {
doBroadcast = false;
}
}
if(doBroadcast) {
broadcastAccountChange(rowId);
}
} else if (matched == ACCOUNTS_STATUS_ID) {
broadcastRegistrationChange(rowId);
}
}
if (matched == FILTERS || matched == FILTERS_ID) {
Filter.resetCache();
}
return count;
}
/**
* Build a {@link Cursor} with a single row that contains all values
* provided through the given {@link ContentValues}.
*/
private Cursor getCursor(ContentValues[] contentValues) {
if(contentValues.length > 0) {
final Set<Entry<String, Object>> valueSet = contentValues[0].valueSet();
int colSize = valueSet.size();
final String[] keys = new String[colSize];
int i = 0;
for (Entry<String, Object> entry : valueSet) {
keys[i] = entry.getKey();
i++;
}
final MatrixCursor cursor = new MatrixCursor(keys);
for (ContentValues cv : contentValues) {
final Object[] values = new Object[colSize];
i = 0;
for (Entry<String, Object> entry : cv.valueSet()) {
values[i] = entry.getValue();
i++;
}
cursor.addRow(values);
}
return cursor;
}
return null;
}
/**
* Broadcast the fact that account config has changed
* @param accountId
*/
private void broadcastAccountChange(long accountId) {
Intent publishIntent = new Intent(SipManager.ACTION_SIP_ACCOUNT_CHANGED);
publishIntent.putExtra(SipProfile.FIELD_ID, accountId);
getContext().sendBroadcast(publishIntent);
BackupWrapper.getInstance(getContext()).dataChanged();
}
/**
* Broadcast the fact that account config has been deleted
* @param accountId
*/
private void broadcastAccountDelete(long accountId) {
Intent publishIntent = new Intent(SipManager.ACTION_SIP_ACCOUNT_DELETED);
publishIntent.putExtra(SipProfile.FIELD_ID, accountId);
getContext().sendBroadcast(publishIntent);
BackupWrapper.getInstance(getContext()).dataChanged();
}
/**
* Broadcast the fact that registration / adding status changed
* @param accountId the id of the account
*/
private void broadcastRegistrationChange(long accountId) {
Intent publishIntent = new Intent(SipManager.ACTION_SIP_REGISTRATION_CHANGED);
publishIntent.putExtra(SipProfile.FIELD_ID, accountId);
getContext().sendBroadcast(publishIntent, SipManager.PERMISSION_USE_SIP);
}
private static List<String> getPossibleFieldsForType(int type){
List<String> possibles = null;
switch (type) {
case ACCOUNTS:
case ACCOUNTS_ID:
possibles = Arrays.asList(ACCOUNT_FULL_PROJECTION);
break;
case CALLLOGS:
case CALLLOGS_ID:
possibles = Arrays.asList(CALL_LOG_FULL_PROJECTION);
break;
case FILTERS:
case FILTERS_ID:
possibles = Arrays.asList(FILTERS_FULL_PROJECTION);
break;
case MESSAGES:
case MESSAGES_ID:
possibles = Arrays.asList(MESSAGES_FULL_PROJECTION);
break;
case THREADS:
case THREADS_ID:
possibles = new ArrayList<String>();
break;
case ACCOUNTS_STATUS:
case ACCOUNTS_STATUS_ID:
possibles = new ArrayList<String>();
default:
}
return possibles;
}
private static void checkSelection(List<String> possibles, String selection) {
if(selection != null) {
String cleanSelection = selection.toLowerCase();
for(String field : possibles) {
cleanSelection = cleanSelection.replace(field, "");
}
cleanSelection = cleanSelection.replaceAll(" in \\([0-9 ,]+\\)", "");
cleanSelection = cleanSelection.replaceAll(" and ", "");
cleanSelection = cleanSelection.replaceAll(" or ", "");
cleanSelection = cleanSelection.replaceAll("[0-9]+", "");
cleanSelection = cleanSelection.replaceAll("[=? ]", "");
if(cleanSelection.length() > 0) {
throw new SecurityException("You are selecting wrong thing " + cleanSelection);
}
}
}
private static void checkProjection(List<String> possibles, String[] projection) {
if(projection != null) {
// Ensure projection is valid
for(String proj : projection) {
proj = proj.replaceAll(" AS [a-zA-Z0-9_]+$", "");
if(!possibles.contains(proj)) {
throw new SecurityException("You are asking wrong values " + proj);
}
}
}
}
}