Refactor ContactsCursorLoader to implement factory pattern.

Utilization of the factory pattern will enable us to more easily change what contacts we present to the user for a specific screen in the future instead of continuing to modify and potentially introduce bugs to this screen.
This commit is contained in:
Alex Hart 2021-03-26 09:24:40 -03:00
parent e068fde8f2
commit 243b4b9414
4 changed files with 297 additions and 184 deletions

View file

@ -58,6 +58,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller; import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.components.emoji.WarningTextView; import org.thoughtcrime.securesms.components.emoji.WarningTextView;
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.ContactChip; import org.thoughtcrime.securesms.contacts.ContactChip;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter; import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem; import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
@ -130,6 +131,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
private HorizontalScrollView chipGroupScrollContainer; private HorizontalScrollView chipGroupScrollContainer;
private WarningTextView groupLimit; private WarningTextView groupLimit;
private OnSelectionLimitReachedListener onSelectionLimitReachedListener; private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider;
@Nullable private FixedViewsAdapter headerAdapter; @Nullable private FixedViewsAdapter headerAdapter;
@ -162,6 +164,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
if (context instanceof OnSelectionLimitReachedListener) { if (context instanceof OnSelectionLimitReachedListener) {
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context; onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context;
} }
if (context instanceof AbstractContactsCursorLoaderFactoryProvider) {
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
}
if (getParentFragment() instanceof AbstractContactsCursorLoaderFactoryProvider) {
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
}
} }
@Override @Override
@ -388,9 +398,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
@Override @Override
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) { public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
FragmentActivity activity = requireActivity(); FragmentActivity activity = requireActivity();
return new ContactsCursorLoader(activity, int displayMode = activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL);
activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL), boolean displayRecents = activity.getIntent().getBooleanExtra(RECENTS, false);
cursorFilter, activity.getIntent().getBooleanExtra(RECENTS, false));
if (cursorFactoryProvider != null) {
return cursorFactoryProvider.get().create();
} else {
return new ContactsCursorLoader.Factory(activity, displayMode, cursorFilter, displayRecents).create();
}
} }
@Override @Override
@ -696,4 +711,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
public interface ScrollCallback { public interface ScrollCallback {
void onBeginScroll(); void onBeginScroll();
} }
public interface AbstractContactsCursorLoaderFactoryProvider {
@NonNull AbstractContactsCursorLoader.Factory get();
}
} }

View file

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.database.MergeCursor;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.content.CursorLoader;
import java.util.List;
public abstract class AbstractContactsCursorLoader extends CursorLoader {
private final String filter;
protected AbstractContactsCursorLoader(@NonNull Context context, @Nullable String filter) {
super(context);
this.filter = sanitizeFilter(filter);
}
@Override
public final Cursor loadInBackground() {
List<Cursor> cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults()
: getFilteredResults();
if (cursorList.size() > 0) {
return new MergeCursor(cursorList.toArray(new Cursor[0]));
}
return null;
}
protected final String getFilter() {
return filter;
}
protected abstract List<Cursor> getUnfilteredResults();
protected abstract List<Cursor> getFilteredResults();
private static @NonNull String sanitizeFilter(@Nullable String filter) {
if (filter == null) {
return "";
} else if (filter.startsWith("@")) {
return filter.substring(1);
} else {
return filter;
}
}
public interface Factory {
@NonNull AbstractContactsCursorLoader create();
}
}

View file

@ -20,13 +20,8 @@ import android.Manifest;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.provider.ContactsContract;
import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.content.CursorLoader;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@ -37,7 +32,6 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FeatureFlags;
@ -51,7 +45,7 @@ import java.util.List;
* *
* @author Jake McGinty * @author Jake McGinty
*/ */
public class ContactsCursorLoader extends CursorLoader { public class ContactsCursorLoader extends AbstractContactsCursorLoader {
private static final String TAG = ContactsCursorLoader.class.getSimpleName(); private static final String TAG = ContactsCursorLoader.class.getSimpleName();
@ -67,57 +61,27 @@ public class ContactsCursorLoader extends CursorLoader {
public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_ACTIVE_GROUPS | FLAG_INACTIVE_GROUPS | FLAG_SELF; public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_ACTIVE_GROUPS | FLAG_INACTIVE_GROUPS | FLAG_SELF;
} }
private static final String[] CONTACT_PROJECTION = new String[]{ContactRepository.ID_COLUMN,
ContactRepository.NAME_COLUMN,
ContactRepository.NUMBER_COLUMN,
ContactRepository.NUMBER_TYPE_COLUMN,
ContactRepository.LABEL_COLUMN,
ContactRepository.CONTACT_TYPE_COLUMN,
ContactRepository.ABOUT_COLUMN};
private static final int RECENT_CONVERSATION_MAX = 25; private static final int RECENT_CONVERSATION_MAX = 25;
private final String filter;
private final int mode; private final int mode;
private final boolean recents; private final boolean recents;
private final ContactRepository contactRepository; private final ContactRepository contactRepository;
public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents) private ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents)
{ {
super(context); super(context, filter);
if (flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS) && !flagSet(mode, DisplayMode.FLAG_ACTIVE_GROUPS)) { if (flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS) && !flagSet(mode, DisplayMode.FLAG_ACTIVE_GROUPS)) {
throw new AssertionError("Inactive group flag set, but the active group flag isn't!"); throw new AssertionError("Inactive group flag set, but the active group flag isn't!");
} }
this.filter = sanitizeFilter(filter);
this.mode = mode; this.mode = mode;
this.recents = recents; this.recents = recents;
this.contactRepository = new ContactRepository(context); this.contactRepository = new ContactRepository(context);
} }
@Override protected final List<Cursor> getUnfilteredResults() {
public Cursor loadInBackground() {
List<Cursor> cursorList = TextUtils.isEmpty(filter) ? getUnfilteredResults()
: getFilteredResults();
if (cursorList.size() > 0) {
return new MergeCursor(cursorList.toArray(new Cursor[0]));
}
return null;
}
private static @NonNull String sanitizeFilter(@Nullable String filter) {
if (filter == null) {
return "";
} else if (filter.startsWith("@")) {
return filter.substring(1);
} else {
return filter;
}
}
private List<Cursor> getUnfilteredResults() {
ArrayList<Cursor> cursorList = new ArrayList<>(); ArrayList<Cursor> cursorList = new ArrayList<>();
if (groupsOnly(mode)) { if (groupsOnly(mode)) {
@ -131,7 +95,7 @@ public class ContactsCursorLoader extends CursorLoader {
return cursorList; return cursorList;
} }
private List<Cursor> getFilteredResults() { protected final List<Cursor> getFilteredResults() {
ArrayList<Cursor> cursorList = new ArrayList<>(); ArrayList<Cursor> cursorList = new ArrayList<>();
addContactsSection(cursorList); addContactsSection(cursorList);
@ -153,7 +117,7 @@ public class ContactsCursorLoader extends CursorLoader {
Cursor recentConversations = getRecentConversationsCursor(); Cursor recentConversations = getRecentConversationsCursor();
if (recentConversations.getCount() > 0) { if (recentConversations.getCount() > 0) {
cursorList.add(getRecentsHeaderCursor()); cursorList.add(ContactsCursorRows.forRecentsHeader(getContext()));
cursorList.add(recentConversations); cursorList.add(recentConversations);
} }
} }
@ -162,7 +126,7 @@ public class ContactsCursorLoader extends CursorLoader {
List<Cursor> contacts = getContactsCursors(); List<Cursor> contacts = getContactsCursors();
if (!isCursorListEmpty(contacts)) { if (!isCursorListEmpty(contacts)) {
cursorList.add(getContactsHeaderCursor()); cursorList.add(ContactsCursorRows.forContactsHeader(getContext()));
cursorList.addAll(contacts); cursorList.addAll(contacts);
} }
} }
@ -175,7 +139,7 @@ public class ContactsCursorLoader extends CursorLoader {
Cursor groups = getRecentConversationsCursor(true); Cursor groups = getRecentConversationsCursor(true);
if (groups.getCount() > 0) { if (groups.getCount() > 0) {
cursorList.add(getRecentsHeaderCursor()); cursorList.add(ContactsCursorRows.forRecentsHeader(getContext()));
cursorList.add(groups); cursorList.add(groups);
} }
} }
@ -188,89 +152,28 @@ public class ContactsCursorLoader extends CursorLoader {
Cursor groups = getGroupsCursor(); Cursor groups = getGroupsCursor();
if (groups.getCount() > 0) { if (groups.getCount() > 0) {
cursorList.add(getGroupsHeaderCursor()); cursorList.add(ContactsCursorRows.forGroupsHeader(getContext()));
cursorList.add(groups); cursorList.add(groups);
} }
} }
private void addNewNumberSection(@NonNull List<Cursor> cursorList) { private void addNewNumberSection(@NonNull List<Cursor> cursorList) {
if (FeatureFlags.usernames() && NumberUtil.isVisuallyValidNumberOrEmail(filter)) { if (FeatureFlags.usernames() && NumberUtil.isVisuallyValidNumberOrEmail(getFilter())) {
cursorList.add(getPhoneNumberSearchHeaderCursor()); cursorList.add(ContactsCursorRows.forPhoneNumberSearchHeader(getContext()));
cursorList.add(getNewNumberCursor()); cursorList.add(getNewNumberCursor());
} else if (!FeatureFlags.usernames() && NumberUtil.isValidSmsOrEmail(filter)){ } else if (!FeatureFlags.usernames() && NumberUtil.isValidSmsOrEmail(getFilter())) {
cursorList.add(getPhoneNumberSearchHeaderCursor()); cursorList.add(ContactsCursorRows.forPhoneNumberSearchHeader(getContext()));
cursorList.add(getNewNumberCursor()); cursorList.add(getNewNumberCursor());
} }
} }
private void addUsernameSearchSection(@NonNull List<Cursor> cursorList) { private void addUsernameSearchSection(@NonNull List<Cursor> cursorList) {
if (FeatureFlags.usernames() && UsernameUtil.isValidUsernameForSearch(filter)) { if (FeatureFlags.usernames() && UsernameUtil.isValidUsernameForSearch(getFilter())) {
cursorList.add(getUsernameSearchHeaderCursor()); cursorList.add(ContactsCursorRows.forUsernameSearchHeader(getContext()));
cursorList.add(getUsernameSearchCursor()); cursorList.add(getUsernameSearchCursor());
} }
} }
private Cursor getRecentsHeaderCursor() {
MatrixCursor recentsHeader = new MatrixCursor(CONTACT_PROJECTION);
recentsHeader.addRow(new Object[]{ null,
getContext().getString(R.string.ContactsCursorLoader_recent_chats),
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE,
"" });
return recentsHeader;
}
private Cursor getContactsHeaderCursor() {
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
contactsHeader.addRow(new Object[] { null,
getContext().getString(R.string.ContactsCursorLoader_contacts),
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE,
"" });
return contactsHeader;
}
private Cursor getGroupsHeaderCursor() {
MatrixCursor groupHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
groupHeader.addRow(new Object[]{ null,
getContext().getString(R.string.ContactsCursorLoader_groups),
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE,
"" });
return groupHeader;
}
private Cursor getPhoneNumberSearchHeaderCursor() {
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
contactsHeader.addRow(new Object[] { null,
getContext().getString(R.string.ContactsCursorLoader_phone_number_search),
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE,
"" });
return contactsHeader;
}
private Cursor getUsernameSearchHeaderCursor() {
MatrixCursor contactsHeader = new MatrixCursor(CONTACT_PROJECTION, 1);
contactsHeader.addRow(new Object[] { null,
getContext().getString(R.string.ContactsCursorLoader_username_search),
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE,
"" });
return contactsHeader;
}
private Cursor getRecentConversationsCursor() { private Cursor getRecentConversationsCursor() {
return getRecentConversationsCursor(false); return getRecentConversationsCursor(false);
} }
@ -278,21 +181,12 @@ public class ContactsCursorLoader extends CursorLoader {
private Cursor getRecentConversationsCursor(boolean groupsOnly) { private Cursor getRecentConversationsCursor(boolean groupsOnly) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext()); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext());
MatrixCursor recentConversations = new MatrixCursor(CONTACT_PROJECTION, RECENT_CONVERSATION_MAX); MatrixCursor recentConversations = ContactsCursorRows.createMatrixCursor(RECENT_CONVERSATION_MAX);
try (Cursor rawConversations = threadDatabase.getRecentConversationList(RECENT_CONVERSATION_MAX, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), groupsOnly, hideGroupsV1(mode), !smsEnabled(mode))) { try (Cursor rawConversations = threadDatabase.getRecentConversationList(RECENT_CONVERSATION_MAX, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), groupsOnly, hideGroupsV1(mode), !smsEnabled(mode))) {
ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations); ThreadDatabase.Reader reader = threadDatabase.readerFor(rawConversations);
ThreadRecord threadRecord; ThreadRecord threadRecord;
while ((threadRecord = reader.getNext()) != null) { while ((threadRecord = reader.getNext()) != null) {
Recipient recipient = threadRecord.getRecipient(); recentConversations.addRow(ContactsCursorRows.forRecipient(getContext(), threadRecord.getRecipient()));
String stringId = recipient.isGroup() ? recipient.requireGroupId().toString() : recipient.getE164().transform(PhoneNumberFormatter::prettyPrint).or(recipient.getEmail()).or("");
recentConversations.addRow(new Object[] { recipient.getId().serialize(),
recipient.getDisplayName(getContext()),
stringId,
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.RECENT_TYPE | (recipient.isRegistered() && !recipient.isForceSmsSelection() ? ContactRepository.PUSH_TYPE : 0),
recipient.getCombinedAboutAndEmoji() });
} }
} }
return recentConversations; return recentConversations;
@ -306,56 +200,34 @@ public class ContactsCursorLoader extends CursorLoader {
} }
if (pushEnabled(mode)) { if (pushEnabled(mode)) {
cursorList.add(contactRepository.querySignalContacts(filter, selfEnabled(mode))); cursorList.add(contactRepository.querySignalContacts(getFilter(), selfEnabled(mode)));
} }
if (pushEnabled(mode) && smsEnabled(mode)) { if (pushEnabled(mode) && smsEnabled(mode)) {
cursorList.add(contactRepository.queryNonSignalContacts(filter)); cursorList.add(contactRepository.queryNonSignalContacts(getFilter()));
} else if (smsEnabled(mode)) { } else if (smsEnabled(mode)) {
cursorList.add(filterNonPushContacts(contactRepository.queryNonSignalContacts(filter))); cursorList.add(filterNonPushContacts(contactRepository.queryNonSignalContacts(getFilter())));
} }
return cursorList; return cursorList;
} }
private Cursor getGroupsCursor() { private Cursor getGroupsCursor() {
MatrixCursor groupContacts = new MatrixCursor(CONTACT_PROJECTION); MatrixCursor groupContacts = ContactsCursorRows.createMatrixCursor();
try (GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(getContext()).getGroupsFilteredByTitle(filter, flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode))) { try (GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(getContext()).getGroupsFilteredByTitle(getFilter(), flagSet(mode, DisplayMode.FLAG_INACTIVE_GROUPS), hideGroupsV1(mode))) {
GroupDatabase.GroupRecord groupRecord; GroupDatabase.GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
groupContacts.addRow(new Object[] { groupRecord.getRecipientId().serialize(), groupContacts.addRow(ContactsCursorRows.forGroup(groupRecord));
groupRecord.getTitle(),
groupRecord.getId(),
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"",
ContactRepository.NORMAL_TYPE,
"" });
} }
} }
return groupContacts; return groupContacts;
} }
private Cursor getNewNumberCursor() { private Cursor getNewNumberCursor() {
MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1); return ContactsCursorRows.forNewNumber(getUnknownContactTitle(), getFilter());
newNumberCursor.addRow(new Object[] { null,
getUnknownContactTitle(),
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactRepository.NEW_PHONE_TYPE,
"" });
return newNumberCursor;
} }
private Cursor getUsernameSearchCursor() { private Cursor getUsernameSearchCursor() {
MatrixCursor cursor = new MatrixCursor(CONTACT_PROJECTION, 1); return ContactsCursorRows.forUsernameSearch(getUnknownContactTitle(), getFilter());
cursor.addRow(new Object[] { null,
getUnknownContactTitle(),
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactRepository.NEW_USERNAME_TYPE,
"" });
return cursor;
} }
private String getUnknownContactTitle() { private String getUnknownContactTitle() {
@ -371,19 +243,13 @@ public class ContactsCursorLoader extends CursorLoader {
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) { private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
try { try {
final long startMillis = System.currentTimeMillis(); final long startMillis = System.currentTimeMillis();
final MatrixCursor matrix = new MatrixCursor(CONTACT_PROJECTION); final MatrixCursor matrix = ContactsCursorRows.createMatrixCursor();
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
final RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN))); final RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)));
final Recipient recipient = Recipient.resolved(id); final Recipient recipient = Recipient.resolved(id);
if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) { if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) {
matrix.addRow(new Object[]{cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)), matrix.addRow(ContactsCursorRows.forNonPushContact(cursor));
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN)),
ContactRepository.NORMAL_TYPE,
"" });
} }
} }
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms"); Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
@ -440,4 +306,24 @@ public class ContactsCursorLoader extends CursorLoader {
private static boolean flagSet(int mode, int flag) { private static boolean flagSet(int mode, int flag) {
return (mode & flag) > 0; return (mode & flag) > 0;
} }
public static class Factory implements AbstractContactsCursorLoader.Factory {
private final Context context;
private final int displayMode;
private final String cursorFilter;
private final boolean displayRecents;
public Factory(Context context, int displayMode, String cursorFilter, boolean displayRecents) {
this.context = context;
this.displayMode = displayMode;
this.cursorFilter = cursorFilter;
this.displayRecents = displayRecents;
}
@Override
public @NonNull AbstractContactsCursorLoader create() {
return new ContactsCursorLoader(context, displayMode, cursorFilter, displayRecents);
}
}
} }

View file

@ -0,0 +1,153 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.ContactsContract;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient;
/**
* Helper utility for generating cursors and cursor rows for subclasses of {@link AbstractContactsCursorLoader}.
*/
public final class ContactsCursorRows {
private static final String[] CONTACT_PROJECTION = new String[]{ContactRepository.ID_COLUMN,
ContactRepository.NAME_COLUMN,
ContactRepository.NUMBER_COLUMN,
ContactRepository.NUMBER_TYPE_COLUMN,
ContactRepository.LABEL_COLUMN,
ContactRepository.CONTACT_TYPE_COLUMN,
ContactRepository.ABOUT_COLUMN};
/**
* Create a {@link MatrixCursor} with the proper projection for a subclass of {@link AbstractContactsCursorLoader}
*/
public static @NonNull MatrixCursor createMatrixCursor() {
return new MatrixCursor(CONTACT_PROJECTION);
}
/**
* Create a {@link MatrixCursor} with the proper projection for a subclass of {@link AbstractContactsCursorLoader}
*
* @param initialCapacity The initial capacity to hand to the {@link MatrixCursor}
*/
public static @NonNull MatrixCursor createMatrixCursor(int initialCapacity) {
return new MatrixCursor(CONTACT_PROJECTION, initialCapacity);
}
/**
* Create a row for a contacts cursor based off the given recipient.
*/
public static @NonNull Object[] forRecipient(@NonNull Context context, @NonNull Recipient recipient) {
String stringId = recipient.isGroup() ? recipient.requireGroupId().toString()
: recipient.getE164().transform(PhoneNumberFormatter::prettyPrint).or(recipient.getEmail()).or("");
return new Object[]{recipient.getId().serialize(),
recipient.getDisplayName(context),
stringId,
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.RECENT_TYPE | (recipient.isRegistered() && !recipient.isForceSmsSelection() ? ContactRepository.PUSH_TYPE : 0),
recipient.getCombinedAboutAndEmoji()};
}
/**
* Create a row for a contacts cursor based off the given system contact.
*/
public static @NonNull Object[] forNonPushContact(@NonNull Cursor systemContactCursor) {
return new Object[]{systemContactCursor.getLong(systemContactCursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)),
systemContactCursor.getString(systemContactCursor.getColumnIndexOrThrow(ContactRepository.NAME_COLUMN)),
systemContactCursor.getString(systemContactCursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN)),
systemContactCursor.getString(systemContactCursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN)),
systemContactCursor.getString(systemContactCursor.getColumnIndexOrThrow(ContactRepository.LABEL_COLUMN)),
ContactRepository.NORMAL_TYPE,
""};
}
/**
* Create a row for a contacts cursor based off the given group record.
*/
public static @NonNull Object[] forGroup(@NonNull GroupDatabase.GroupRecord groupRecord) {
return new Object[]{groupRecord.getRecipientId().serialize(),
groupRecord.getTitle(),
groupRecord.getId(),
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"",
ContactRepository.NORMAL_TYPE,
""};
}
/**
* Create a row for a contacts cursor for a new number the user is entering or has entered.
*/
public static @NonNull MatrixCursor forNewNumber(@NonNull String unknownContactTitle, @NonNull String filter) {
MatrixCursor matrixCursor = createMatrixCursor(1);
matrixCursor.addRow(new Object[]{null,
unknownContactTitle,
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactRepository.NEW_PHONE_TYPE,
""});
return matrixCursor;
}
/**
* Create a row for a contacts cursor for a username the user is entering or has entered.
*/
public static @NonNull MatrixCursor forUsernameSearch(@NonNull String unknownContactTitle, @NonNull String filter) {
MatrixCursor matrixCursor = createMatrixCursor(1);
matrixCursor.addRow(new Object[]{null,
unknownContactTitle,
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactRepository.NEW_USERNAME_TYPE,
""});
return matrixCursor;
}
public static @NonNull MatrixCursor forUsernameSearchHeader(@NonNull Context context) {
return forHeader(context.getString(R.string.ContactsCursorLoader_username_search));
}
public static @NonNull MatrixCursor forPhoneNumberSearchHeader(@NonNull Context context) {
return forHeader(context.getString(R.string.ContactsCursorLoader_phone_number_search));
}
public static @NonNull MatrixCursor forGroupsHeader(@NonNull Context context) {
return forHeader(context.getString(R.string.ContactsCursorLoader_groups));
}
public static @NonNull MatrixCursor forRecentsHeader(@NonNull Context context) {
return forHeader(context.getString(R.string.ContactsCursorLoader_recent_chats));
}
public static @NonNull MatrixCursor forContactsHeader(@NonNull Context context) {
return forHeader(context.getString(R.string.ContactsCursorLoader_contacts));
}
public static @NonNull MatrixCursor forHeader(@NonNull String name) {
MatrixCursor matrixCursor = createMatrixCursor(1);
matrixCursor.addRow(new Object[]{null,
name,
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactRepository.DIVIDER_TYPE,
""});
return matrixCursor;
}
}