Add recent chats to top of share list

This commit is contained in:
Moxie Marlinspike 2017-11-16 15:21:46 -08:00
parent c5a9f27c31
commit cf81815bf6
14 changed files with 258 additions and 87 deletions

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<padding android:top="1dp" android:right="0dp" android:bottom="0dp" android:left="0dp" />
<solid android:color="#101010" />
</shape>
</item>
<item>
<shape>
<padding android:top="1dp" android:right="0dp" android:bottom="0dp" android:left="0dp" />
<solid android:color="#222222" />
<size android:height="20dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<padding android:top="1dp" android:right="0dp" android:bottom="0dp" android:left="0dp" />
<solid android:color="#d4d4d4" />
</shape>
</item>
<item>
<shape>
<padding android:top="1dp" android:right="0dp" android:bottom="0dp" android:left="0dp" />
<solid android:color="@color/gray5" />
<size android:height="20dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/contact_list_divider">
<TextView android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textSize="14sp"
android:textColor="@color/signal_primary_dark"
tools:text="Recent chats"/>
</LinearLayout>

View File

@ -13,7 +13,6 @@
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingTop="4dp"
android:clipToPadding="false" android:clipToPadding="false"
android:scrollbars="vertical" /> android:scrollbars="vertical" />

View File

@ -132,6 +132,8 @@
<attr name="search_toolbar_background" format="color"/> <attr name="search_toolbar_background" format="color"/>
<attr name="contact_list_divider" format="reference"/>
<declare-styleable name="ColorPreference"> <declare-styleable name="ColorPreference">
<attr name="itemLayout" format="reference" /> <attr name="itemLayout" format="reference" />
<attr name="choices" format="reference" /> <attr name="choices" format="reference" />

View File

@ -1249,6 +1249,8 @@
<string name="UnauthorizedReminder_device_no_longer_registered">Device no longer registered</string> <string name="UnauthorizedReminder_device_no_longer_registered">Device no longer registered</string>
<string name="UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device">This is likely because you registered your phone number with Signal on a different device. Tap to re-register.</string> <string name="UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device">This is likely because you registered your phone number with Signal on a different device. Tap to re-register.</string>
<string name="OutdatedBuildReminder_no_web_browser_installed">No web browser installed!</string> <string name="OutdatedBuildReminder_no_web_browser_installed">No web browser installed!</string>
<string name="ContactsCursorLoader_recent_chats">Recent chats</string>
<string name="ContactsCursorLoader_contacts">Contacts</string>
<!-- EOF --> <!-- EOF -->

View File

@ -143,6 +143,8 @@
<item name="conversation_number_picker_text_color_normal">@color/gray65</item> <item name="conversation_number_picker_text_color_normal">@color/gray65</item>
<item name="conversation_number_picker_text_color_selected">@color/black</item> <item name="conversation_number_picker_text_color_selected">@color/black</item>
<item name="contact_list_divider">@drawable/contact_list_divider_light</item>
<item name="verification_background">@color/gray5</item> <item name="verification_background">@color/gray5</item>
<item name="emoji_tab_strip_background">@color/gray12</item> <item name="emoji_tab_strip_background">@color/gray12</item>
@ -256,6 +258,8 @@
<item name="conversation_item_last_seen_background">#66333333</item> <item name="conversation_item_last_seen_background">#66333333</item>
<item name="conversation_item_last_seen_text_background">@drawable/last_seen_divider_text_background_dark</item> <item name="conversation_item_last_seen_text_background">@drawable/last_seen_divider_text_background_dark</item>
<item name="contact_list_divider">@drawable/contact_list_divider_dark</item>
<item name="verification_background">#ff333333</item> <item name="verification_background">#ff333333</item>
<item name="dialog_info_icon">@drawable/ic_info_outline_dark</item> <item name="dialog_info_icon">@drawable/ic_info_outline_dark</item>

View File

@ -38,12 +38,13 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Set;
/** /**
* Fragment for selecting a one or more contacts from a list. * Fragment for selecting a one or more contacts from a list.
@ -54,11 +55,13 @@ import java.util.Map;
public class ContactSelectionListFragment extends Fragment public class ContactSelectionListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor> implements LoaderManager.LoaderCallbacks<Cursor>
{ {
@SuppressWarnings("unused")
private static final String TAG = ContactSelectionListFragment.class.getSimpleName(); private static final String TAG = ContactSelectionListFragment.class.getSimpleName();
public final static String DISPLAY_MODE = "display_mode"; public static final String DISPLAY_MODE = "display_mode";
public final static String MULTI_SELECT = "multi_select"; public static final String MULTI_SELECT = "multi_select";
public final static String REFRESHABLE = "refreshable"; 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_ALL = ContactsCursorLoader.MODE_ALL;
public final static int DISPLAY_MODE_PUSH_ONLY = ContactsCursorLoader.MODE_PUSH_ONLY; public final static int DISPLAY_MODE_PUSH_ONLY = ContactsCursorLoader.MODE_PUSH_ONLY;
@ -66,7 +69,7 @@ public class ContactSelectionListFragment extends Fragment
private TextView emptyText; private TextView emptyText;
private Map<Long, String> selectedContacts; private Set<String> selectedContacts;
private OnContactSelectedListener onContactSelectedListener; private OnContactSelectedListener onContactSelectedListener;
private SwipeRefreshLayout swipeRefresh; private SwipeRefreshLayout swipeRefresh;
private String cursorFilter; private String cursorFilter;
@ -108,7 +111,7 @@ public class ContactSelectionListFragment extends Fragment
public @NonNull List<String> getSelectedContacts() { public @NonNull List<String> getSelectedContacts() {
List<String> selected = new LinkedList<>(); List<String> selected = new LinkedList<>();
if (selectedContacts != null) { if (selectedContacts != null) {
selected.addAll(selectedContacts.values()); selected.addAll(selectedContacts);
} }
return selected; return selected;
@ -151,9 +154,9 @@ public class ContactSelectionListFragment extends Fragment
@Override @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new ContactsCursorLoader(getActivity(), return new ContactsCursorLoader(getActivity(), KeyCachingService.getMasterSecret(getContext()),
getActivity().getIntent().getIntExtra(DISPLAY_MODE, DISPLAY_MODE_ALL), getActivity().getIntent().getIntExtra(DISPLAY_MODE, DISPLAY_MODE_ALL),
cursorFilter); cursorFilter, getActivity().getIntent().getBooleanExtra(RECENTS, false));
} }
@Override @Override
@ -177,13 +180,12 @@ public class ContactSelectionListFragment extends Fragment
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener { private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
@Override @Override
public void onItemClick(ContactSelectionListItem contact) { public void onItemClick(ContactSelectionListItem contact) {
if (!isMulti() || !selectedContacts.contains(contact.getNumber())) {
if (!isMulti() || !selectedContacts.containsKey(contact.getContactId())) { selectedContacts.add(contact.getNumber());
selectedContacts.put(contact.getContactId(), contact.getNumber());
contact.setChecked(true); contact.setChecked(true);
if (onContactSelectedListener != null) onContactSelectedListener.onContactSelected(contact.getNumber()); if (onContactSelectedListener != null) onContactSelectedListener.onContactSelected(contact.getNumber());
} else { } else {
selectedContacts.remove(contact.getContactId()); selectedContacts.remove(contact.getNumber());
contact.setChecked(false); contact.setChecked(false);
if (onContactSelectedListener != null) onContactSelectedListener.onContactDeselected(contact.getNumber()); if (onContactSelectedListener != null) onContactSelectedListener.onContactDeselected(contact.getNumber());
} }

View File

@ -100,6 +100,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
: ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY); : ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY);
} }
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
getIntent().putExtra(ContactSelectionListFragment.RECENTS, true);
setContentView(R.layout.share_activity); setContentView(R.layout.share_activity);
initializeToolbar(); initializeToolbar();

View File

@ -43,7 +43,9 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapte
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* List adapter to display all contacts and their related information * List adapter to display all contacts and their related information
@ -56,6 +58,9 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
{ {
private final static String TAG = ContactSelectionListAdapter.class.getSimpleName(); private final static String TAG = ContactSelectionListAdapter.class.getSimpleName();
private static final int VIEW_TYPE_CONTACT = 0;
private static final int VIEW_TYPE_DIVIDER = 1;
private final static int STYLE_ATTRIBUTES[] = new int[]{R.attr.contact_selection_push_user, private final static int STYLE_ATTRIBUTES[] = new int[]{R.attr.contact_selection_push_user,
R.attr.contact_selection_lay_user}; R.attr.contact_selection_lay_user};
@ -65,10 +70,21 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
private final ItemClickListener clickListener; private final ItemClickListener clickListener;
private final GlideRequests glideRequests; private final GlideRequests glideRequests;
private final HashMap<Long, String> selectedContacts = new HashMap<>(); private final Set<String> selectedContacts = new HashSet<>();
public static class ViewHolder extends RecyclerView.ViewHolder { public abstract static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(@NonNull final View itemView,
public ViewHolder(View itemView) {
super(itemView);
}
public abstract void bind(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect);
public abstract void unbind(@NonNull GlideRequests glideRequests);
public abstract void setChecked(boolean checked);
}
public static class ContactViewHolder extends ViewHolder {
ContactViewHolder(@NonNull final View itemView,
@Nullable final ItemClickListener clickListener) @Nullable final ItemClickListener clickListener)
{ {
super(itemView); super(itemView);
@ -80,10 +96,45 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
public ContactSelectionListItem getView() { public ContactSelectionListItem getView() {
return (ContactSelectionListItem) itemView; return (ContactSelectionListItem) itemView;
} }
public void bind(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect) {
getView().set(glideRequests, type, name, number, label, color, multiSelect);
}
@Override
public void unbind(@NonNull GlideRequests glideRequests) {
getView().unbind(glideRequests);
}
@Override
public void setChecked(boolean checked) {
getView().setChecked(checked);
}
} }
public static class HeaderViewHolder extends RecyclerView.ViewHolder { public static class DividerViewHolder extends ViewHolder {
public HeaderViewHolder(View itemView) {
private final TextView label;
DividerViewHolder(View itemView) {
super(itemView);
this.label = itemView.findViewById(R.id.label);
}
@Override
public void bind(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect) {
this.label.setText(name);
}
@Override
public void unbind(@NonNull GlideRequests glideRequests) {}
@Override
public void setChecked(boolean checked) {}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
HeaderViewHolder(View itemView) {
super(itemView); super(itemView);
} }
} }
@ -106,17 +157,23 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
public long getHeaderId(int i) { public long getHeaderId(int i) {
if (!isActiveCursor()) return -1; if (!isActiveCursor()) return -1;
return Util.hashCode(getHeaderString(i), isPush(i)); int contactType = getContactType(i);
if (contactType == ContactsDatabase.DIVIDER_TYPE) return -1;
return Util.hashCode(getHeaderString(i), getContactType(i));
} }
@Override @Override
public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(li.inflate(R.layout.contact_selection_list_item, parent, false), clickListener); if (viewType == VIEW_TYPE_CONTACT) {
return new ContactViewHolder(li.inflate(R.layout.contact_selection_list_item, parent, false), clickListener);
} else {
return new DividerViewHolder(li.inflate(R.layout.contact_selection_list_divider, parent, false));
}
} }
@Override @Override
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) { public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN));
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN)); int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)); String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN)); String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
@ -128,11 +185,21 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
int color = (contactType == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) : int color = (contactType == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
drawables.getColor(1, 0xff000000); drawables.getColor(1, 0xff000000);
viewHolder.getView().unbind(glideRequests); viewHolder.unbind(glideRequests);
viewHolder.getView().set(glideRequests, id, contactType, name, number, labelText, color, multiSelect); viewHolder.bind(glideRequests, contactType, name, number, labelText, color, multiSelect);
viewHolder.getView().setChecked(selectedContacts.containsKey(id)); viewHolder.setChecked(selectedContacts.contains(number));
} }
@Override
public int getItemViewType(@NonNull Cursor cursor) {
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN)) == ContactsDatabase.DIVIDER_TYPE) {
return VIEW_TYPE_DIVIDER;
} else {
return VIEW_TYPE_CONTACT;
}
}
@Override @Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false)); return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
@ -145,7 +212,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
@Override @Override
public void onItemViewRecycled(ViewHolder holder) { public void onItemViewRecycled(ViewHolder holder) {
holder.getView().unbind(glideRequests); holder.unbind(glideRequests);
} }
@Override @Override
@ -153,7 +220,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
return getHeaderString(position); return getHeaderString(position);
} }
public Map<Long, String> getSelectedContacts() { public Set<String> getSelectedContacts() {
return selectedContacts; return selectedContacts;
} }
@ -169,8 +236,15 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
} }
private @NonNull String getHeaderString(int position) { private @NonNull String getHeaderString(int position) {
int contactType = getContactType(position);
if (contactType == ContactsDatabase.RECENT_TYPE || contactType == ContactsDatabase.DIVIDER_TYPE) {
return " ";
}
Cursor cursor = getCursorAtPositionOrThrow(position); Cursor cursor = getCursorAtPositionOrThrow(position);
String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)); String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
if (!TextUtils.isEmpty(letter)) { if (!TextUtils.isEmpty(letter)) {
String firstChar = letter.trim().substring(0, 1).toUpperCase(); String firstChar = letter.trim().substring(0, 1).toUpperCase();
if (Character.isLetterOrDigit(firstChar.codePointAt(0))) { if (Character.isLetterOrDigit(firstChar.codePointAt(0))) {
@ -181,9 +255,13 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
return "#"; return "#";
} }
private boolean isPush(int position) { private int getContactType(int position) {
final Cursor cursor = getCursorAtPositionOrThrow(position); final Cursor cursor = getCursorAtPositionOrThrow(position);
return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN)) == ContactsDatabase.PUSH_TYPE; return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
}
private boolean isPush(int position) {
return getContactType(position) == ContactsDatabase.PUSH_TYPE;
} }
public interface ItemClickListener { public interface ItemClickListener {

View File

@ -12,7 +12,6 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
@ -21,6 +20,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
public class ContactSelectionListItem extends LinearLayout implements RecipientModifiedListener { public class ContactSelectionListItem extends LinearLayout implements RecipientModifiedListener {
@SuppressWarnings("unused")
private static final String TAG = ContactSelectionListItem.class.getSimpleName(); private static final String TAG = ContactSelectionListItem.class.getSimpleName();
private AvatarImageView contactPhotoImage; private AvatarImageView contactPhotoImage;
@ -29,7 +29,6 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM
private TextView labelView; private TextView labelView;
private CheckBox checkBox; private CheckBox checkBox;
private long id;
private String number; private String number;
private Recipient recipient; private Recipient recipient;
private GlideRequests glideRequests; private GlideRequests glideRequests;
@ -54,9 +53,8 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM
ViewUtil.setTextViewGravityStart(this.nameView, getContext()); ViewUtil.setTextViewGravityStart(this.nameView, getContext());
} }
public void set(@NonNull GlideRequests glideRequests, long id, int type, String name, String number, String label, int color, boolean multiSelect) { public void set(@NonNull GlideRequests glideRequests, int type, String name, String number, String label, int color, boolean multiSelect) {
this.glideRequests = glideRequests; this.glideRequests = glideRequests;
this.id = id;
this.number = number; this.number = number;
if (type == ContactsDatabase.NEW_TYPE) { if (type == ContactsDatabase.NEW_TYPE) {
@ -114,10 +112,6 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM
this.nameView.setText(name); this.nameView.setText(name);
} }
public long getContactId() {
return id;
}
public String getNumber() { public String getNumber() {
return number; return number;
} }

View File

@ -1,5 +1,5 @@
/** /*
* Copyright (C) 2013 Open Whisper Systems * Copyright (C) 2013-2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -27,9 +27,13 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
@ -44,24 +48,59 @@ public class ContactsCursorLoader extends CursorLoader {
private static final String TAG = ContactsCursorLoader.class.getSimpleName(); private static final String TAG = ContactsCursorLoader.class.getSimpleName();
public final static int MODE_ALL = 0; public static final int MODE_ALL = 0;
public final static int MODE_PUSH_ONLY = 1; public static final int MODE_PUSH_ONLY = 1;
public final static int MODE_SMS_ONLY = 2; public static final int MODE_SMS_ONLY = 2;
private final String filter; private static final String[] CONTACT_PROJECTION = new String[]{ContactsDatabase.NAME_COLUMN,
private final int mode; ContactsDatabase.NUMBER_COLUMN,
ContactsDatabase.NUMBER_TYPE_COLUMN,
ContactsDatabase.LABEL_COLUMN,
ContactsDatabase.CONTACT_TYPE_COLUMN};
public ContactsCursorLoader(Context context, int mode, String filter) {
private final MasterSecret masterSecret;
private final String filter;
private final int mode;
private final boolean recents;
public ContactsCursorLoader(@NonNull Context context, @NonNull MasterSecret masterSecret,
int mode, String filter, boolean recents)
{
super(context); super(context);
this.filter = filter; this.masterSecret = masterSecret;
this.mode = mode; this.filter = filter;
this.mode = mode;
this.recents = recents;
} }
@Override @Override
public Cursor loadInBackground() { public Cursor loadInBackground() {
ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext()); ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext());
ArrayList<Cursor> cursorList = new ArrayList<>(3); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(getContext());
ArrayList<Cursor> cursorList = new ArrayList<>(4);
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});
ThreadDatabase.Reader reader = threadDatabase.readerFor(recentConversations, new MasterCipher(masterSecret));
ThreadRecord threadRecord;
while ((threadRecord = reader.getNext()) != null) {
synthesizedContacts.addRow(new Object[] {threadRecord.getRecipient().toShortString(),
threadRecord.getRecipient().getAddress().serialize(),
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"", ContactsDatabase.RECENT_TYPE});
}
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);
}
}
if (mode != MODE_SMS_ONLY) { if (mode != MODE_SMS_ONLY) {
cursorList.add(contactsDatabase.queryTextSecureContacts(filter)); cursorList.add(contactsDatabase.queryTextSecureContacts(filter));
@ -74,14 +113,9 @@ public class ContactsCursorLoader extends CursorLoader {
} }
if (!TextUtils.isEmpty(filter) && NumberUtil.isValidSmsOrEmail(filter)) { if (!TextUtils.isEmpty(filter) && NumberUtil.isValidSmsOrEmail(filter)) {
MatrixCursor newNumberCursor = new MatrixCursor(new String[] {ContactsDatabase.ID_COLUMN, MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1);
ContactsDatabase.NAME_COLUMN,
ContactsDatabase.NUMBER_COLUMN,
ContactsDatabase.NUMBER_TYPE_COLUMN,
ContactsDatabase.LABEL_COLUMN,
ContactsDatabase.CONTACT_TYPE_COLUMN}, 1);
newNumberCursor.addRow(new Object[] {-1L, getContext().getString(R.string.contact_selection_list__unknown_contact), newNumberCursor.addRow(new Object[] {getContext().getString(R.string.contact_selection_list__unknown_contact),
filter, ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM, filter, ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2", ContactsDatabase.NEW_TYPE}); "\u21e2", ContactsDatabase.NEW_TYPE});
@ -94,19 +128,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(new String[]{ContactsDatabase.ID_COLUMN, final MatrixCursor matrix = new MatrixCursor(CONTACT_PROJECTION);
ContactsDatabase.NAME_COLUMN,
ContactsDatabase.NUMBER_COLUMN,
ContactsDatabase.NUMBER_TYPE_COLUMN,
ContactsDatabase.LABEL_COLUMN,
ContactsDatabase.CONTACT_TYPE_COLUMN});
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
final String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN)); final String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
final Recipient recipient = Recipient.from(getContext(), Address.fromExternal(getContext(), number), false); final Recipient recipient = Recipient.from(getContext(), Address.fromExternal(getContext(), number), false);
if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) { if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) {
matrix.addRow(new Object[]{cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN)), matrix.addRow(new Object[]{cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)),
number, number,
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN)), cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN)), cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN)),

View File

@ -62,16 +62,17 @@ public class ContactsDatabase {
private static final String CALL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call"; private static final String CALL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call";
private static final String SYNC = "__TS"; private static final String SYNC = "__TS";
static final String ID_COLUMN = "_id";
static final String NAME_COLUMN = "name"; static final String NAME_COLUMN = "name";
static final String NUMBER_COLUMN = "number"; static final String NUMBER_COLUMN = "number";
static final String NUMBER_TYPE_COLUMN = "number_type"; static final String NUMBER_TYPE_COLUMN = "number_type";
static final String LABEL_COLUMN = "label"; static final String LABEL_COLUMN = "label";
static final String CONTACT_TYPE_COLUMN = "contact_type"; static final String CONTACT_TYPE_COLUMN = "contact_type";
static final int NORMAL_TYPE = 0; static final int NORMAL_TYPE = 0;
static final int PUSH_TYPE = 1; static final int PUSH_TYPE = 1;
static final int NEW_TYPE = 2; static final int NEW_TYPE = 2;
static final int RECENT_TYPE = 3;
static final int DIVIDER_TYPE = 4;
private final Context context; private final Context context;
@ -142,8 +143,7 @@ public class ContactsDatabase {
uri = uri.buildUpon().appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build(); uri = uri.buildUpon().appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build();
} }
String[] projection = new String[]{ContactsContract.CommonDataKinds.Phone._ID, String[] projection = new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL}; ContactsContract.CommonDataKinds.Phone.LABEL};
@ -151,7 +151,6 @@ public class ContactsDatabase {
String sort = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; String sort = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
Map<String, String> projectionMap = new HashMap<String, String>() {{ Map<String, String> projectionMap = new HashMap<String, String>() {{
put(ID_COLUMN, ContactsContract.CommonDataKinds.Phone._ID);
put(NAME_COLUMN, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME); put(NAME_COLUMN, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
put(NUMBER_COLUMN, ContactsContract.CommonDataKinds.Phone.NUMBER); put(NUMBER_COLUMN, ContactsContract.CommonDataKinds.Phone.NUMBER);
put(NUMBER_TYPE_COLUMN, ContactsContract.CommonDataKinds.Phone.TYPE); put(NUMBER_TYPE_COLUMN, ContactsContract.CommonDataKinds.Phone.TYPE);
@ -180,14 +179,12 @@ public class ContactsDatabase {
} }
@NonNull Cursor queryTextSecureContacts(String filter) { @NonNull Cursor queryTextSecureContacts(String filter) {
String[] projection = new String[] {ContactsContract.Data._ID, String[] projection = new String[] {ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Data.DATA1}; ContactsContract.Data.DATA1};
String sort = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; String sort = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
Map<String, String> projectionMap = new HashMap<String, String>(){{ Map<String, String> projectionMap = new HashMap<String, String>(){{
put(ID_COLUMN, ContactsContract.Data._ID);
put(NAME_COLUMN, ContactsContract.Contacts.DISPLAY_NAME); put(NAME_COLUMN, ContactsContract.Contacts.DISPLAY_NAME);
put(NUMBER_COLUMN, ContactsContract.Data.DATA1); put(NUMBER_COLUMN, ContactsContract.Data.DATA1);
}}; }};

View File

@ -288,16 +288,6 @@ public class ThreadDatabase extends Database {
}}; }};
} }
// public void setUnread(long threadId, int unreadCount) {
// ContentValues contentValues = new ContentValues(1);
// contentValues.put(READ, 0);
// contentValues.put(UNREAD_COUNT, unreadCount);
//
// SQLiteDatabase db = databaseHelper.getWritableDatabase();
// db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
// notifyConversationListListeners();
// }
public void incrementUnread(long threadId, int amount) { public void incrementUnread(long threadId, int amount) {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
@ -351,7 +341,7 @@ public class ThreadDatabase extends Database {
selectionArgs[i++] = DelimiterUtil.escape(address.serialize(), ' '); selectionArgs[i++] = DelimiterUtil.escape(address.serialize(), ' ');
} }
String query = createQuery(selection); String query = createQuery(selection, 0);
cursors.add(db.rawQuery(query, selectionArgs)); cursors.add(db.rawQuery(query, selectionArgs));
} }
@ -360,6 +350,13 @@ public class ThreadDatabase extends Database {
return cursor; return cursor;
} }
public Cursor getRecentConversationList(int limit) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = createQuery(MESSAGE_COUNT + " != 0", limit);
return db.rawQuery(query, null);
}
public Cursor getConversationList() { public Cursor getConversationList() {
return getConversationList("0"); return getConversationList("0");
} }
@ -370,7 +367,7 @@ public class ThreadDatabase extends Database {
private Cursor getConversationList(String archived) { private Cursor getConversationList(String archived) {
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = createQuery(ARCHIVED + " = ? AND " + MESSAGE_COUNT + " != 0"); String query = createQuery(ARCHIVED + " = ? AND " + MESSAGE_COUNT + " != 0", 0);
Cursor cursor = db.rawQuery(query, new String[]{archived}); Cursor cursor = db.rawQuery(query, new String[]{archived});
setNotifyConverationListListeners(cursor); setNotifyConverationListListeners(cursor);
@ -380,7 +377,7 @@ public class ThreadDatabase extends Database {
public Cursor getDirectShareList() { public Cursor getDirectShareList() {
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = createQuery(MESSAGE_COUNT + " != 0"); String query = createQuery(MESSAGE_COUNT + " != 0", 0);
return db.rawQuery(query, null); return db.rawQuery(query, null);
} }
@ -598,15 +595,22 @@ public class ThreadDatabase extends Database {
return thumbnail != null ? thumbnail.getThumbnailUri() : null; return thumbnail != null ? thumbnail.getThumbnailUri() : null;
} }
private @NonNull String createQuery(@NonNull String where) { private @NonNull String createQuery(@NonNull String where, int limit) {
String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ","); String projection = Util.join(COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION, ",");
return "SELECT " + projection + " FROM " + TABLE_NAME + String query =
"SELECT " + projection + " FROM " + TABLE_NAME +
" LEFT OUTER JOIN " + RecipientDatabase.TABLE_NAME + " LEFT OUTER JOIN " + RecipientDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS +
" LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME +
" ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID + " ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID +
" WHERE " + where + " WHERE " + where +
" ORDER BY " + TABLE_NAME + "." + DATE + " DESC"; " ORDER BY " + TABLE_NAME + "." + DATE + " DESC";
if (limit > 0) {
query += " LIMIT " + limit;
}
return query;
} }
public interface ProgressListener { public interface ProgressListener {