Clean up contact queries.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-07-14 14:31:03 -07:00
parent d1940fe0f9
commit 704f2b91e2
7 changed files with 206 additions and 358 deletions

View File

@ -32,9 +32,10 @@ import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.TextView;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.LinkedList;
import java.util.List;
@ -140,11 +141,10 @@ public class PushContactSelectionListFragment extends Fragment
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (getActivity().getIntent().getBooleanExtra(PushContactSelectionActivity.PUSH_ONLY_EXTRA, false)) {
return ContactAccessor.getInstance().getCursorLoaderForPushContacts(getActivity(), cursorFilter);
} else {
return ContactAccessor.getInstance().getCursorLoaderForContacts(getActivity(), cursorFilter);
}
boolean pushOnly = getActivity().getIntent().getBooleanExtra(PushContactSelectionActivity.PUSH_ONLY_EXTRA, false);
boolean supportsSms = TextSecurePreferences.isSmsEnabled(getActivity());
return new ContactsCursorLoader(getActivity(), !pushOnly && supportsSms, cursorFilter);
}
@Override

View File

@ -23,13 +23,9 @@ import android.database.MergeCursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.telephony.PhoneNumberUtils;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -65,35 +61,6 @@ public class ContactAccessor {
return instance;
}
public CursorLoader getCursorLoaderForContactsWithNumbers(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
return new CursorLoader(context, uri, null, selection, null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
public CursorLoader getCursorLoaderForContactGroups(Context context) {
return new CursorLoader(context, ContactsContract.Groups.CONTENT_URI,
null, null, null, ContactsContract.Groups.TITLE + " ASC");
}
public Loader<Cursor> getCursorLoaderForContacts(Context context, String filter) {
return new ContactsCursorLoader(context, filter, false);
}
public Loader<Cursor> getCursorLoaderForPushContacts(Context context, String filter) {
return new ContactsCursorLoader(context, filter, true);
}
public Cursor getCursorForContactsWithNumbers(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
return context.getContentResolver().query(uri, null, selection, null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
public Collection<ContactData> getContactsWithPush(Context context) {
final ContentResolver resolver = context.getContentResolver();
final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME};
@ -136,34 +103,6 @@ public class ContactAccessor {
return null;
}
public String getNameForNumber(Context context, String number) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst())
return cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public GroupData getGroupData(Context context, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
String title = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
return new GroupData(id, title);
}
public ContactData getContactData(Context context, Cursor cursor) {
return getContactData(context,
cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
}
public ContactData getContactData(Context context, Uri uri) {
return getContactData(context, getNameFromContact(context, uri), Long.parseLong(uri.getLastPathSegment()));
}
@ -193,32 +132,6 @@ public class ContactAccessor {
return contactData;
}
public List<ContactData> getGroupMembership(Context context, long groupId) {
LinkedList<ContactData> contacts = new LinkedList<ContactData>();
Cursor groupMembership = null;
try {
String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
String[] args = new String[] {groupId+"",
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
while (groupMembership != null && groupMembership.moveToNext()) {
String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
contacts.add(getContactData(context, displayName, contactId));
}
} finally {
if (groupMembership != null)
groupMembership.close();
}
return contacts;
}
public List<String> getNumbersForThreadSearchFilter(Context context, String constraint) {
LinkedList<String> numberList = new LinkedList<>();
Cursor cursor = null;
@ -258,26 +171,6 @@ public class ContactAccessor {
return Phone.getTypeLabel(mContext.getResources(), type, label);
}
private long getContactIdFromLookupUri(Context context, Uri uri) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri,
new String[] {ContactsContract.Contacts._ID},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
} else {
return -1;
}
} finally {
if (cursor != null)
cursor.close();
}
}
public static class NumberData implements Parcelable {
public static final Parcelable.Creator<NumberData> CREATOR = new Parcelable.Creator<NumberData>() {
@ -313,16 +206,6 @@ public class ContactAccessor {
}
}
public static class GroupData {
public final long id;
public final String name;
public GroupData(long id, String name) {
this.id = id;
this.name = name;
}
}
public static class ContactData implements Parcelable {
public static final Parcelable.Creator<ContactData> CREATOR = new Parcelable.Creator<ContactData>() {
@ -345,13 +228,6 @@ public class ContactAccessor {
this.numbers = new LinkedList<NumberData>();
}
public ContactData(long id, String name, List<NumberData> numbers) {
this.id = id;
this.name = name;
this.numbers = numbers;
}
public ContactData(Parcel in) {
id = in.readLong();
name = in.readString();

View File

@ -70,21 +70,21 @@ public class ContactSelectionListAdapter extends CursorAdapter
@Override
public void bindView(View view, Context context, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN));
int type = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN));
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN));
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(),
numberType, label).toString();
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN));
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN));
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN));
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(),
numberType, label).toString();
int color = (type == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
drawables.getColor(1, 0xff000000);
int color = (contactType == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
drawables.getColor(1, 0xff000000);
((ContactSelectionListItem)view).unbind();
((ContactSelectionListItem)view).set(id, type, name, number, labelText, color, multiSelect);
((ContactSelectionListItem)view).set(id, contactType, name, number, labelText, color, multiSelect);
((ContactSelectionListItem)view).setChecked(selectedContacts.containsKey(id));
}
@ -105,10 +105,10 @@ public class ContactSelectionListAdapter extends CursorAdapter
cursor.moveToPosition(i);
int type = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
if (type == ContactsDatabase.PUSH_TYPE) holder.text.setText(R.string.contact_selection_list__header_textsecure_users);
else holder.text.setText(R.string.contact_selection_list__header_other);
if (contactType == ContactsDatabase.PUSH_TYPE) holder.text.setText(R.string.contact_selection_list__header_textsecure_users);
else holder.text.setText(R.string.contact_selection_list__header_other);
return convertView;
}
@ -118,7 +118,7 @@ public class ContactSelectionListAdapter extends CursorAdapter
Cursor cursor = getCursor();
cursor.moveToPosition(i);
return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.TYPE_COLUMN));
return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
}
public Map<Long, String> getSelectedContacts() {

View File

@ -18,12 +18,15 @@ package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.database.MergeCursor;
import android.support.v4.content.CursorLoader;
import android.text.TextUtils;
import android.util.Log;
import junit.framework.Assert;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.concurrent.Semaphore;
import java.util.ArrayList;
/**
* CursorLoader that initializes a ContactsDatabase instance
@ -31,47 +34,34 @@ import java.util.concurrent.Semaphore;
* @author Jake McGinty
*/
public class ContactsCursorLoader extends CursorLoader {
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
private static final int DB_PERMITS = 100;
private final Context context;
private final String filter;
private final boolean pushOnly;
private final Semaphore dbSemaphore = new Semaphore(DB_PERMITS);
private ContactsDatabase db;
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
public ContactsCursorLoader(Context context, String filter, boolean pushOnly) {
private final String filter;
private boolean includeSmsContacts;
public ContactsCursorLoader(Context context, boolean includeSmsContacts, String filter) {
super(context);
this.context = context;
this.filter = filter;
this.pushOnly = pushOnly;
this.db = new ContactsDatabase(context);
this.includeSmsContacts = includeSmsContacts;
}
@Override
public Cursor loadInBackground() {
try {
dbSemaphore.acquire();
return db.query(filter, pushOnly);
} catch (InterruptedException ie) {
throw new AssertionError(ie);
} finally {
dbSemaphore.release();
}
}
ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext());
ArrayList<Cursor> cursorList = new ArrayList<>(3);
@Override
public void onReset() {
Log.w(TAG, "onReset()");
try {
dbSemaphore.acquire(DB_PERMITS);
db.close();
db = new ContactsDatabase(context);
} catch (InterruptedException ie) {
throw new AssertionError(ie);
} finally {
dbSemaphore.release(DB_PERMITS);
cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
if (includeSmsContacts) {
cursorList.add(contactsDatabase.querySystemContacts(filter));
}
super.onReset();
if (!TextUtils.isEmpty(filter) && NumberUtil.isValidSmsOrEmail(filter)) {
cursorList.add(contactsDatabase.getNewNumberCursor(filter));
}
return new MergeCursor(cursorList.toArray(new Cursor[0]));
}
}

View File

@ -18,33 +18,26 @@ package org.thoughtcrime.securesms.contacts;
import android.accounts.Account;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -55,46 +48,25 @@ import java.util.Set;
* @author Jake McGinty
*/
public class ContactsDatabase {
private static final String TAG = ContactsDatabase.class.getSimpleName();
private final DatabaseOpenHelper dbHelper;
private final Context context;
public static final String TABLE_NAME = "CONTACTS";
public static final String ID_COLUMN = ContactsContract.CommonDataKinds.Phone._ID;
public static final String NAME_COLUMN = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME;
public static final String NUMBER_TYPE_COLUMN = ContactsContract.CommonDataKinds.Phone.TYPE;
public static final String NUMBER_COLUMN = ContactsContract.CommonDataKinds.Phone.NUMBER;
public static final String LABEL_COLUMN = ContactsContract.CommonDataKinds.Phone.LABEL;
public static final String TYPE_COLUMN = "type";
private static final String FILTER_SELECTION = NAME_COLUMN + " LIKE ? OR " + NUMBER_COLUMN + " LIKE ?";
private static final String CONTACT_LIST_SORT = NAME_COLUMN + " COLLATE NOCASE ASC";
private static final String[] ANDROID_PROJECTION = new String[]{ID_COLUMN,
NAME_COLUMN,
NUMBER_TYPE_COLUMN,
LABEL_COLUMN,
NUMBER_COLUMN};
private static final String[] CONTACTS_PROJECTION = new String[]{ID_COLUMN,
NAME_COLUMN,
NUMBER_TYPE_COLUMN,
LABEL_COLUMN,
NUMBER_COLUMN,
TYPE_COLUMN};
public static final String ID_COLUMN = "_id";
public static final String NAME_COLUMN = "name";
public static final String NUMBER_COLUMN = "number";
public static final String NUMBER_TYPE_COLUMN = "number_type";
public static final String LABEL_COLUMN = "label";
public static final String CONTACT_TYPE_COLUMN = "contact_type";
public static final int NORMAL_TYPE = 0;
public static final int PUSH_TYPE = 1;
public static final int GROUP_TYPE = 2;
private final Context context;
public ContactsDatabase(Context context) {
this.dbHelper = new DatabaseOpenHelper(context);
this.context = context;
}
public void close() {
dbHelper.close();
}
public synchronized void setRegisteredUsers(Account account, List<String> e164numbers)
throws RemoteException, OperationApplicationException
{
@ -153,6 +125,7 @@ public class ContactsDatabase {
.withValueBackReference(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, index)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, e164number)
.withValue(ContactsContract.Data.SYNC2, "__TS")
.build());
operations.add(ContentProviderOperation.newInsert(dataUri)
@ -176,183 +149,184 @@ public class ContactsDatabase {
.withSelection(BaseColumns._ID + " = ?", new String[] {String.valueOf(rowId)})
.build());
}
public @NonNull Cursor querySystemContacts(String filter) {
Uri uri;
public Cursor query(String filter, boolean pushOnly) {
// FIXME: This doesn't make sense to me. You pass in pushOnly, but then
// conditionally check to see whether other contacts should be included
// in the query method itself? I don't think this method should have any
// understanding of that stuff.
final boolean includeAndroidContacts = !pushOnly && TextSecurePreferences.isSmsEnabled(context);
final Cursor localCursor = queryLocalDb(filter);
final Cursor androidCursor;
final MatrixCursor newNumberCursor;
if (includeAndroidContacts) {
androidCursor = queryAndroidDb(filter);
} else {
androidCursor = null;
}
if (!TextUtils.isEmpty(filter) && NumberUtil.isValidSmsOrEmail(filter)) {
newNumberCursor = new MatrixCursor(CONTACTS_PROJECTION, 1);
newNumberCursor.addRow(new Object[]{-1L, context.getString(R.string.contact_selection_list__unknown_contact),
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM, "\u21e2", filter, NORMAL_TYPE});
} else {
newNumberCursor = null;
}
List<Cursor> cursors = new ArrayList<Cursor>();
if (localCursor != null) cursors.add(localCursor);
if (androidCursor != null) cursors.add(androidCursor);
if (newNumberCursor != null) cursors.add(newNumberCursor);
switch (cursors.size()) {
case 0: return null;
case 1: return cursors.get(0);
default: return new MergeCursor(cursors.toArray(new Cursor[]{}));
}
}
private Cursor queryAndroidDb(String filter) {
final Uri baseUri;
if (!TextUtils.isEmpty(filter)) {
baseUri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI,
Uri.encode(filter));
uri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(filter));
} else {
baseUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
}
Cursor cursor = context.getContentResolver().query(baseUri, ANDROID_PROJECTION, null, null, CONTACT_LIST_SORT);
return cursor == null ? null : new TypedCursorWrapper(cursor);
String[] projection = new String[]{ContactsContract.CommonDataKinds.Phone._ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL};
String sort = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE NOCASE ASC";
Map<String, String> projectionMap = new HashMap<String, String>() {{
put(ID_COLUMN, ContactsContract.CommonDataKinds.Phone._ID);
put(NAME_COLUMN, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
put(NUMBER_COLUMN, ContactsContract.CommonDataKinds.Phone.NUMBER);
put(NUMBER_TYPE_COLUMN, ContactsContract.CommonDataKinds.Phone.TYPE);
put(LABEL_COLUMN, ContactsContract.CommonDataKinds.Phone.LABEL);
}};
Cursor cursor = context.getContentResolver().query(uri, projection,
ContactsContract.Data.SYNC2 + " IS NULL OR " +
ContactsContract.Data.SYNC2 + " != ?",
new String[] {"__TS"},
sort);
return new ProjectionMappingCursor(cursor, projectionMap,
new Pair<String, Object>(CONTACT_TYPE_COLUMN, NORMAL_TYPE));
}
private Cursor queryLocalDb(String filter) {
final String selection;
final String[] selectionArgs;
final String fuzzyFilter = "%" + filter + "%";
if (!TextUtils.isEmpty(filter)) {
selection = FILTER_SELECTION;
selectionArgs = new String[]{fuzzyFilter, fuzzyFilter};
public @NonNull Cursor queryTextSecureContacts(String filter) {
String[] projection = new String[] {ContactsContract.Data._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Data.DATA1};
String sort = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE NOCASE ASC";
Map<String, String> projectionMap = new HashMap<String, String>(){{
put(ID_COLUMN, ContactsContract.Data._ID);
put(NAME_COLUMN, ContactsContract.Contacts.DISPLAY_NAME);
put(NUMBER_COLUMN, ContactsContract.Data.DATA1);
}};
Cursor cursor;
if (TextUtils.isEmpty(filter)) {
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + " = ?",
new String[] {"vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact"},
sort);
} else {
selection = null;
selectionArgs = null;
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?",
new String[] {"vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact",
"%" + filter + "%"},
sort);
}
return queryLocalDb(selection, selectionArgs, null);
return new ProjectionMappingCursor(cursor, projectionMap,
new Pair<String, Object>(LABEL_COLUMN, "TextSecure"),
new Pair<String, Object>(NUMBER_TYPE_COLUMN, 0),
new Pair<String, Object>(CONTACT_TYPE_COLUMN, PUSH_TYPE));
}
private Cursor queryLocalDb(String selection, String[] selectionArgs, String[] columns) {
SQLiteDatabase localDb = dbHelper.getReadableDatabase();
final Cursor localCursor;
if (localDb != null) localCursor = localDb.query(TABLE_NAME, columns, selection, selectionArgs, null, null, CONTACT_LIST_SORT);
else localCursor = null;
if (localCursor != null && !localCursor.moveToFirst()) {
localCursor.close();
return null;
}
return localCursor;
public Cursor getNewNumberCursor(String filter) {
MatrixCursor newNumberCursor = new MatrixCursor(new String[] {ID_COLUMN, NAME_COLUMN, NUMBER_COLUMN, NUMBER_TYPE_COLUMN, LABEL_COLUMN, CONTACT_TYPE_COLUMN}, 1);
newNumberCursor.addRow(new Object[]{-1L, context.getString(R.string.contact_selection_list__unknown_contact),
filter, ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2", NORMAL_TYPE});
return newNumberCursor;
}
private static class DatabaseOpenHelper extends SQLiteOpenHelper {
private static class ProjectionMappingCursor extends CursorWrapper {
private final Context context;
private SQLiteDatabase mDatabase;
private final Map<String, String> projectionMap;
private final Pair<String, Object>[] extras;
private static final String TABLE_CREATE =
"CREATE TABLE " + TABLE_NAME + " (" +
ID_COLUMN + " INTEGER PRIMARY KEY, " +
NAME_COLUMN + " TEXT, " +
NUMBER_TYPE_COLUMN + " INTEGER, " +
LABEL_COLUMN + " TEXT, " +
NUMBER_COLUMN + " TEXT, " +
TYPE_COLUMN + " INTEGER);";
DatabaseOpenHelper(Context context) {
super(context, null, null, 1);
this.context = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "onCreate called for contacts database.");
mDatabase = db;
mDatabase.execSQL(TABLE_CREATE);
if (TextSecurePreferences.isPushRegistered(context)) {
try {
loadPushUsers();
} catch (IOException ioe) {
Log.e(TAG, "Issue when trying to load push users into memory db.", ioe);
}
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
private void loadPushUsers() throws IOException {
Log.d(TAG, "populating push users into virtual db.");
Collection<ContactAccessor.ContactData> pushUsers = ContactAccessor.getInstance().getContactsWithPush(context);
for (ContactAccessor.ContactData user : pushUsers) {
ContentValues values = new ContentValues();
values.put(ID_COLUMN, user.id);
values.put(NAME_COLUMN, user.name);
values.put(NUMBER_TYPE_COLUMN, ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM);
values.put(LABEL_COLUMN, (String)null);
values.put(NUMBER_COLUMN, user.numbers.get(0).number);
values.put(TYPE_COLUMN, PUSH_TYPE);
mDatabase.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
Log.d(TAG, "finished populating push users.");
}
}
private static class TypedCursorWrapper extends CursorWrapper {
private final int pushColumnIndex;
public TypedCursorWrapper(Cursor cursor) {
@SafeVarargs
public ProjectionMappingCursor(Cursor cursor,
Map<String, String> projectionMap,
Pair<String, Object>... extras)
{
super(cursor);
pushColumnIndex = cursor.getColumnCount();
this.projectionMap = projectionMap;
this.extras = extras;
}
@Override
public int getColumnCount() {
return super.getColumnCount() + 1;
return super.getColumnCount() + extras.length;
}
@Override
public int getColumnIndex(String columnName) {
if (TYPE_COLUMN.equals(columnName)) return super.getColumnCount();
else return super.getColumnIndex(columnName);
for (int i=0;i<extras.length;i++) {
if (extras[i].first.equals(columnName)) {
return super.getColumnCount() + i;
}
}
return super.getColumnIndex(projectionMap.get(columnName));
}
@Override
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
if (TYPE_COLUMN.equals(columnName)) return super.getColumnCount();
else return super.getColumnIndexOrThrow(columnName);
int index = getColumnIndex(columnName);
if (index == -1) throw new IllegalArgumentException("Bad column name!");
else return index;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex == pushColumnIndex) return TYPE_COLUMN;
else return super.getColumnName(columnIndex);
int baseColumnCount = super.getColumnCount();
if (columnIndex >= baseColumnCount) {
int offset = columnIndex - baseColumnCount;
return extras[offset].first;
}
return getReverseProjection(super.getColumnName(columnIndex));
}
@Override
public String[] getColumnNames() {
final String[] columns = new String[super.getColumnCount() + 1];
System.arraycopy(super.getColumnNames(), 0, columns, 0, super.getColumnCount());
columns[pushColumnIndex] = TYPE_COLUMN;
return columns;
String[] names = super.getColumnNames();
String[] allNames = new String[names.length + extras.length];
for (int i=0;i<names.length;i++) {
allNames[i] = getReverseProjection(names[i]);
}
for (int i=0;i<extras.length;i++) {
allNames[names.length + i] = extras[i].first;
}
return allNames;
}
@Override
public int getInt(int columnIndex) {
if (columnIndex == pushColumnIndex) return NORMAL_TYPE;
else return super.getInt(columnIndex);
if (columnIndex >= super.getColumnCount()) {
int offset = columnIndex - super.getColumnCount();
return (Integer)extras[offset].second;
}
return super.getInt(columnIndex);
}
@Override
public String getString(int columnIndex) {
if (columnIndex >= super.getColumnCount()) {
int offset = columnIndex - super.getColumnCount();
return (String)extras[offset].second;
}
return super.getString(columnIndex);
}
private @Nullable String getReverseProjection(String columnName) {
for (Map.Entry<String, String> entry : projectionMap.entrySet()) {
if (entry.getValue().equals(columnName)) {
return entry.getKey();
}
}
return null;
}
}
}

View File

@ -26,6 +26,7 @@ import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@ -86,6 +87,7 @@ public class DatabaseFactory {
private final PushDatabase pushDatabase;
private final GroupDatabase groupDatabase;
private final RecipientPreferenceDatabase recipientPreferenceDatabase;
private final ContactsDatabase contactsDatabase;
public static DatabaseFactory getInstance(Context context) {
synchronized (lock) {
@ -148,6 +150,10 @@ public class DatabaseFactory {
return getInstance(context).recipientPreferenceDatabase;
}
public static ContactsDatabase getContactsDatabase(Context context) {
return getInstance(context).contactsDatabase;
}
private DatabaseFactory(Context context) {
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
this.sms = new SmsDatabase(context, databaseHelper);
@ -163,6 +169,7 @@ public class DatabaseFactory {
this.pushDatabase = new PushDatabase(context, databaseHelper);
this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.recipientPreferenceDatabase = new RecipientPreferenceDatabase(context, databaseHelper);
this.contactsDatabase = new ContactsDatabase(context);
}
public void reset(Context context) {

View File

@ -12,6 +12,7 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.NotInDirectoryException;
import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
@ -94,7 +95,7 @@ public class DirectoryHelper {
}
try {
new ContactsDatabase(context).setRegisteredUsers(account.get(), e164numbers);
DatabaseFactory.getContactsDatabase(context).setRegisteredUsers(account.get(), e164numbers);
} catch (RemoteException | OperationApplicationException e) {
Log.w(TAG, e);
}