diff --git a/res/values/strings.xml b/res/values/strings.xml
index 65803f27a2..00bde6ef62 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -90,6 +90,7 @@
Recent chats
Contacts
+ Groups
Message %s
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
index 167a5012ee..d61832e59b 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
@@ -23,6 +23,7 @@ import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
@@ -61,10 +62,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override
protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
- getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE,
- TextSecurePreferences.isSmsEnabled(this)
- ? ContactSelectionListFragment.DISPLAY_MODE_ALL
- : ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
+ int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
+ : DisplayMode.FLAG_PUSH | DisplayMode.FLAG_GROUPS;
+ getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
}
setContentView(R.layout.contact_selection_activity);
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
index 3767925d1e..adca058835 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
@@ -75,12 +76,7 @@ public class ContactSelectionListFragment extends Fragment
public static final String REFRESHABLE = "refreshable";
public static final String RECENTS = "recents";
- public final static int DISPLAY_MODE_ALL = ContactsCursorLoader.MODE_ALL;
- public final static int DISPLAY_MODE_PUSH_ONLY = ContactsCursorLoader.MODE_PUSH_ONLY;
- public final static int DISPLAY_MODE_SMS_ONLY = ContactsCursorLoader.MODE_SMS_ONLY;
-
- private TextView emptyText;
-
+ private TextView emptyText;
private Set selectedContacts;
private OnContactSelectedListener onContactSelectedListener;
private SwipeRefreshLayout swipeRefresh;
@@ -221,7 +217,7 @@ public class ContactSelectionListFragment extends Fragment
@Override
public Loader onCreateLoader(int id, Bundle args) {
return new ContactsCursorLoader(getActivity(),
- getActivity().getIntent().getIntExtra(DISPLAY_MODE, DISPLAY_MODE_ALL),
+ getActivity().getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
cursorFilter, getActivity().getIntent().getBooleanExtra(RECENTS, false));
}
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
index df0cc58e89..735aaaadd3 100644
--- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -44,6 +44,7 @@ import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.PushRecipientsPanel;
import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
@@ -314,8 +315,11 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override
public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
- if (groupToUpdate.isPresent()) intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE,
- ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
+ if (groupToUpdate.isPresent()) {
+ intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH);
+ } else {
+ intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH | DisplayMode.FLAG_SMS);
+ }
startActivityForResult(intent, PICK_CONTACT);
}
}
diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java
index 976b3a6c72..74a5478f4d 100644
--- a/src/org/thoughtcrime/securesms/InviteActivity.java
+++ b/src/org/thoughtcrime/securesms/InviteActivity.java
@@ -26,6 +26,7 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -49,7 +50,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
- getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactSelectionListFragment.DISPLAY_MODE_SMS_ONLY);
+ getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java
index 62503f57b4..96195e3e8a 100644
--- a/src/org/thoughtcrime/securesms/ShareActivity.java
+++ b/src/org/thoughtcrime/securesms/ShareActivity.java
@@ -38,6 +38,8 @@ import android.view.View;
import android.widget.ImageView;
import org.thoughtcrime.securesms.components.SearchToolbar;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
+import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
@@ -92,8 +94,8 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE,
TextSecurePreferences.isSmsEnabled(this)
- ? ContactSelectionListFragment.DISPLAY_MODE_ALL
- : ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
+ ? DisplayMode.FLAG_ALL
+ : DisplayMode.FLAG_PUSH | DisplayMode.FLAG_GROUPS);
}
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java
index f6a732bb34..d88f5da512 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java
@@ -30,6 +30,7 @@ import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
@@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList;
+import java.util.List;
/**
* CursorLoader that initializes a ContactsDatabase instance
@@ -48,9 +50,12 @@ public class ContactsCursorLoader extends CursorLoader {
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
- public static final int MODE_ALL = 0;
- public static final int MODE_PUSH_ONLY = 1;
- public static final int MODE_SMS_ONLY = 2;
+ public static final class DisplayMode {
+ public static final int FLAG_PUSH = 1;
+ public static final int FLAG_SMS = 1 << 1;
+ public static final int FLAG_GROUPS = 1 << 2;
+ public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_GROUPS;
+ }
private static final String[] CONTACT_PROJECTION = new String[]{ContactsDatabase.NAME_COLUMN,
ContactsDatabase.NUMBER_COLUMN,
@@ -59,9 +64,9 @@ public class ContactsCursorLoader extends CursorLoader {
ContactsDatabase.CONTACT_TYPE_COLUMN};
- private final String filter;
- private final int mode;
- private final boolean recents;
+ private final String filter;
+ private final int mode;
+ private final boolean recents;
public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents)
{
@@ -74,55 +79,148 @@ public class ContactsCursorLoader extends CursorLoader {
@Override
public Cursor loadInBackground() {
- ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext());
- ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext());
- ArrayList cursorList = new ArrayList<>(4);
+ List cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults()
+ : getFilteredResults();
+ if (cursorList.size() > 0) {
+ return new MergeCursor(cursorList.toArray(new Cursor[0]));
+ }
+ return null;
+ }
- if (recents && TextUtils.isEmpty(filter)) {
- try (Cursor recentConversations = DatabaseFactory.getThreadDatabase(getContext()).getRecentConversationList(5)) {
- MatrixCursor synthesizedContacts = new MatrixCursor(CONTACT_PROJECTION);
- synthesizedContacts.addRow(new Object[] {getContext().getString(R.string.ContactsCursorLoader_recent_chats), "", ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", ContactsDatabase.DIVIDER_TYPE});
+ private List getUnfilteredResults() {
+ ArrayList cursorList = new ArrayList<>();
- ThreadDatabase.Reader reader = threadDatabase.readerFor(recentConversations);
+ if (recents) {
+ Cursor recentConversations = getRecentConversationsCursor();
+ if (recentConversations.getCount() > 0) {
+ cursorList.add(getRecentsHeaderCursor());
+ cursorList.add(recentConversations);
+ cursorList.add(getContactsHeaderCursor());
+ }
+ }
+ cursorList.addAll(getContactsCursors());
+ return cursorList;
+ }
- ThreadRecord threadRecord;
+ private List getFilteredResults() {
+ ArrayList cursorList = new ArrayList<>();
- while ((threadRecord = reader.getNext()) != null) {
- synthesizedContacts.addRow(new Object[] {threadRecord.getRecipient().toShortString(),
- threadRecord.getRecipient().getAddress().serialize(),
- ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
- "", ContactsDatabase.RECENT_TYPE});
+ if (groupsEnabled(mode)) {
+ Cursor groups = getGroupsCursor();
+ if (groups.getCount() > 0) {
+ List contacts = getContactsCursors();
+ if (!isCursorListEmpty(contacts)) {
+ cursorList.add(getContactsHeaderCursor());
+ cursorList.addAll(contacts);
+ cursorList.add(getGroupsHeaderCursor());
}
-
- synthesizedContacts.addRow(new Object[] {getContext().getString(R.string.ContactsCursorLoader_contacts), "", ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", ContactsDatabase.DIVIDER_TYPE});
- if (synthesizedContacts.getCount() > 2) cursorList.add(synthesizedContacts);
+ cursorList.add(groups);
+ } else {
+ cursorList.addAll(getContactsCursors());
}
+ } else {
+ cursorList.addAll(getContactsCursors());
}
- if (Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
- if (mode != MODE_SMS_ONLY) {
- cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
- }
-
- if (mode == MODE_ALL) {
- cursorList.add(contactsDatabase.querySystemContacts(filter));
- } else if (mode == MODE_SMS_ONLY) {
- cursorList.add(filterNonPushContacts(contactsDatabase.querySystemContacts(filter)));
- }
+ if (NumberUtil.isValidSmsOrEmail(filter)) {
+ cursorList.add(getNewNumberCursor());
}
- if (!TextUtils.isEmpty(filter) && NumberUtil.isValidSmsOrEmail(filter)) {
- MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1);
+ return cursorList;
+ }
- newNumberCursor.addRow(new Object[] {getContext().getString(R.string.contact_selection_list__unknown_contact),
- filter, ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
- "\u21e2", ContactsDatabase.NEW_TYPE});
+ private Cursor getRecentsHeaderCursor() {
+ MatrixCursor recentsHeader = new MatrixCursor(CONTACT_PROJECTION);
+ recentsHeader.addRow(new Object[]{ getContext().getString(R.string.ContactsCursorLoader_recent_chats),
+ "",
+ ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
+ "",
+ ContactsDatabase.DIVIDER_TYPE });
+ return recentsHeader;
+ }
- cursorList.add(newNumberCursor);
+ private Cursor getContactsHeaderCursor() {
+ MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
+ contactsHeader.addRow(new Object[] { getContext().getString(R.string.ContactsCursorLoader_contacts),
+ "",
+ ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
+ "",
+ ContactsDatabase.DIVIDER_TYPE });
+ return contactsHeader;
+ }
+
+ private Cursor getGroupsHeaderCursor() {
+ MatrixCursor groupHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
+ groupHeader.addRow(new Object[]{ getContext().getString(R.string.ContactsCursorLoader_groups),
+ "",
+ ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
+ "",
+ ContactsDatabase.DIVIDER_TYPE });
+ return groupHeader;
+ }
+
+
+ private Cursor getRecentConversationsCursor() {
+ ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext());
+
+ MatrixCursor recentConversations = new MatrixCursor(CONTACT_PROJECTION, 5);
+ try (Cursor rawConversations = threadDatabase.getRecentConversationList(5)) {
+ ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations);
+ ThreadRecord threadRecord;
+ while ((threadRecord = reader.getNext()) != null) {
+ recentConversations.addRow(new Object[] { threadRecord.getRecipient().toShortString(),
+ threadRecord.getRecipient().getAddress().serialize(),
+ ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
+ "",
+ ContactsDatabase.RECENT_TYPE });
+ }
+ }
+ return recentConversations;
+ }
+
+ private List getContactsCursors() {
+ ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext());
+ List cursorList = new ArrayList<>(2);
+
+ if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
+ return cursorList;
}
- if (cursorList.size() > 0) return new MergeCursor(cursorList.toArray(new Cursor[0]));
- else return null;
+ if (pushEnabled(mode)) {
+ cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
+ }
+
+ if (pushEnabled(mode) && smsEnabled(mode)) {
+ cursorList.add(contactsDatabase.querySystemContacts(filter));
+ } else if (smsEnabled(mode)) {
+ cursorList.add(filterNonPushContacts(contactsDatabase.querySystemContacts(filter)));
+ }
+ return cursorList;
+ }
+
+ private Cursor getGroupsCursor() {
+ MatrixCursor groupContacts = new MatrixCursor(CONTACT_PROJECTION);
+ try (GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(getContext()).getGroupsFilteredByTitle(filter)) {
+ GroupDatabase.GroupRecord groupRecord;
+ while ((groupRecord = reader.getNext()) != null) {
+ groupContacts.addRow(new Object[] { groupRecord.getTitle(),
+ groupRecord.getEncodedId(),
+ ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
+ "",
+ ContactsDatabase.NORMAL_TYPE });
+ }
+ }
+ return groupContacts;
+ }
+
+ private Cursor getNewNumberCursor() {
+ MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1);
+ newNumberCursor.addRow(new Object[] { getContext().getString(R.string.contact_selection_list__unknown_contact),
+ filter,
+ ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
+ "\u21e2",
+ ContactsDatabase.NEW_TYPE });
+ return newNumberCursor;
}
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
@@ -147,4 +245,24 @@ public class ContactsCursorLoader extends CursorLoader {
cursor.close();
}
}
+
+ private static boolean isCursorListEmpty(List list) {
+ int sum = 0;
+ for (Cursor cursor : list) {
+ sum += cursor.getCount();
+ }
+ return sum == 0;
+ }
+
+ private static boolean pushEnabled(int mode) {
+ return (mode & DisplayMode.FLAG_PUSH) > 0;
+ }
+
+ private static boolean smsEnabled(int mode) {
+ return (mode & DisplayMode.FLAG_SMS) > 0;
+ }
+
+ private static boolean groupsEnabled(int mode) {
+ return (mode & DisplayMode.FLAG_GROUPS) > 0;
+ }
}
diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
index 411975659f..490d3ee030 100644
--- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
+import java.io.Closeable;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -304,7 +305,7 @@ public class GroupDatabase extends Database {
}
}
- public static class Reader {
+ public static class Reader implements Closeable {
private final Cursor cursor;
@@ -338,6 +339,7 @@ public class GroupDatabase extends Database {
cursor.getInt(cursor.getColumnIndexOrThrow(MMS)) == 1);
}
+ @Override
public void close() {
if (this.cursor != null)
this.cursor.close();