From 704f2b91e2fd7ecbe2b0c142c4f10e46c287b9f4 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 14 Jul 2015 14:31:03 -0700 Subject: [PATCH] Clean up contact queries. // FREEBIE --- .../PushContactSelectionListFragment.java | 12 +- .../securesms/contacts/ContactAccessor.java | 124 ------- .../contacts/ContactSelectionListAdapter.java | 30 +- .../contacts/ContactsCursorLoader.java | 58 ++- .../securesms/contacts/ContactsDatabase.java | 330 ++++++++---------- .../securesms/database/DatabaseFactory.java | 7 + .../securesms/util/DirectoryHelper.java | 3 +- 7 files changed, 206 insertions(+), 358 deletions(-) diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java index cfff9ebb1b..db440ecae9 100644 --- a/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java @@ -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 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 diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java index fe45889002..99a3c07112 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -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 getCursorLoaderForContacts(Context context, String filter) { - return new ContactsCursorLoader(context, filter, false); - } - - public Loader 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 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 getGroupMembership(Context context, long groupId) { - LinkedList contacts = new LinkedList(); - 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 getNumbersForThreadSearchFilter(Context context, String constraint) { LinkedList 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 CREATOR = new Parcelable.Creator() { @@ -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 CREATOR = new Parcelable.Creator() { @@ -345,13 +228,6 @@ public class ContactAccessor { this.numbers = new LinkedList(); } - public ContactData(long id, String name, List numbers) { - this.id = id; - this.name = name; - this.numbers = numbers; - } - - public ContactData(Parcel in) { id = in.readLong(); name = in.readString(); diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java index ad1b8d3151..7f1ae268b1 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java @@ -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 getSelectedContacts() { diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 9c57bbd42c..1757dfdaea 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -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 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])); } } diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java index bf21a45845..b30801a151 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java @@ -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 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 cursors = new ArrayList(); - 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 projectionMap = new HashMap() {{ + 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(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 projectionMap = new HashMap(){{ + 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(LABEL_COLUMN, "TextSecure"), + new Pair(NUMBER_TYPE_COLUMN, 0), + new Pair(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 projectionMap; + private final Pair[] 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 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 projectionMap, + Pair... 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= 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= 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 entry : projectionMap.entrySet()) { + if (entry.getValue().equals(columnName)) { + return entry.getKey(); + } + } + + return null; } } } diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 64dfa215b7..1fbaa0269d 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java index 578a1dfe9d..059e25487e 100644 --- a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java +++ b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java @@ -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); }