diff --git a/res/layout/new_conversation_activity.xml b/res/layout/new_conversation_activity.xml
index e289f89b17..e3bf93e1f4 100644
--- a/res/layout/new_conversation_activity.xml
+++ b/res/layout/new_conversation_activity.xml
@@ -5,10 +5,6 @@
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
-
-
+
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center|center_vertical"
+ android:layout_marginTop="15dp"
+ android:text="@string/contact_selection_group_activity__finding_contacts"
+ android:textSize="20sp" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f1a6f219ce..fc0c87bb7a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -147,7 +147,6 @@
New MMS Group
You have selected a contact that doesn\'t support TextSecure groups, so this group will be MMS.
You\'re not registered for using the data channel, so TextSecure groups are disabled.
- You\'re not the owner of this group, so you cannot edit the title or picture.
An unexpected error happened that has made group creation fail.
You need at least one person in your group!
One of the members of your group has a number that can\'t be read correctly. Please fix or remove that contact and try again.
@@ -155,6 +154,7 @@
Group Avatar
Create Group
Creating %1$s…
+ Cannot add non-TextSecure contacts to an existing TextSecure group
Import System SMS Database?
@@ -765,6 +765,7 @@
Unselect All
TEXTSECURE USERS
ALL CONTACTS
+ New message to...
Finished
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
index f83750854f..c0191edc9c 100644
--- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -162,9 +162,17 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
}
private void addSelectedContact(Recipient contact) {
+ final boolean isPushUser = isActiveInDirectory(this, contact);
+ if (existingContacts != null && !isPushUser) {
+ Toast.makeText(getApplicationContext(),
+ R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group,
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
if (!selectedContacts.contains(contact) && (existingContacts == null || !existingContacts.contains(contact)))
selectedContacts.add(contact);
- if (!isActiveInDirectory(this, contact)) {
+ if (!isPushUser) {
disableWhisperGroupUi(R.string.GroupCreateActivity_contacts_dont_support_push);
getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title);
}
@@ -375,6 +383,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
@Override
public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
+ if (existingContacts != null) intent.putExtra(PushContactSelectionActivity.PUSH_ONLY_EXTRA, true);
startActivityForResult(intent, PICK_CONTACT);
}
}
@@ -533,9 +542,13 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
@Override
protected Pair doInBackground(Void... params) {
byte[] avatarBytes = null;
- if (avatarBmp != null) {
+ final Bitmap bitmap;
+ if (avatarBmp == null) bitmap = existingAvatarBmp;
+ else bitmap = avatarBmp;
+
+ if (bitmap != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
- avatarBmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
avatarBytes = stream.toByteArray();
}
final String name = (groupName.getText() != null) ? groupName.getText().toString() : null;
diff --git a/src/org/thoughtcrime/securesms/NewConversationActivity.java b/src/org/thoughtcrime/securesms/NewConversationActivity.java
index 5e871d1c5d..57ca481d78 100644
--- a/src/org/thoughtcrime/securesms/NewConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/NewConversationActivity.java
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.widget.EditText;
import android.widget.Toast;
import com.actionbarsherlock.app.ActionBar;
@@ -62,7 +63,6 @@ public class NewConversationActivity extends PassphraseRequiredSherlockFragmentA
private final DynamicTheme dynamicTheme = new DynamicTheme();
private MasterSecret masterSecret;
- private SingleRecipientPanel recipientsPanel;
private PushContactSelectionListFragment contactsFragment;
@Override
@@ -106,7 +106,6 @@ public class NewConversationActivity extends PassphraseRequiredSherlockFragmentA
}
private void initializeResources() {
- recipientsPanel = (SingleRecipientPanel) findViewById(R.id.recipients);
contactsFragment = (PushContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(new PushContactSelectionListFragment.OnContactSelectedListener() {
@Override
@@ -116,15 +115,6 @@ public class NewConversationActivity extends PassphraseRequiredSherlockFragmentA
openNewConversation(recipients);
}
});
-
- recipientsPanel.setPanelChangeListener(new SingleRecipientPanel.RecipientsPanelChangedListener() {
- @Override
- public void onRecipientsPanelUpdate(Recipients recipients) {
- Log.i(TAG, "Choosing contact from autocompletion.");
- openNewConversation(recipients);
- }
- });
-
}
private void handleSelectionFinished() {
diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java b/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java
index dcd38dee37..e61281a899 100644
--- a/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java
+++ b/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java
@@ -56,7 +56,8 @@ import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
*
*/
public class PushContactSelectionActivity extends PassphraseRequiredSherlockFragmentActivity {
- private final static String TAG = "ContactSelectActivity";
+ private final static String TAG = "ContactSelectActivity";
+ public final static String PUSH_ONLY_EXTRA = "push_only";
private final DynamicTheme dynamicTheme = new DynamicTheme();
diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java
index 6bbbc8e595..87f15a2f19 100644
--- a/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java
+++ b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java
@@ -24,11 +24,14 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
+import android.widget.EditText;
import android.widget.TextView;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
@@ -36,6 +39,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolder;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.DataHolder;
+import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import java.util.LinkedList;
import java.util.List;
@@ -61,6 +65,8 @@ public class PushContactSelectionListFragment extends Fragment
private OnContactSelectedListener onContactSelectedListener;
private boolean multi = false;
private StickyListHeadersListView listView;
+ private EditText filterEditText;
+ private String cursorFilter;
@Override
@@ -80,6 +86,12 @@ public class PushContactSelectionListFragment extends Fragment
super.onPause();
}
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ ContactsDatabase.destroyInstance();
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.push_contact_selection_list_activity, container, false);
@@ -127,9 +139,27 @@ public class PushContactSelectionListFragment extends Fragment
listView = (StickyListHeadersListView) getView().findViewById(android.R.id.list);
listView.setFocusable(true);
listView.setFastScrollEnabled(true);
- listView.setFastScrollAlwaysVisible(true);
listView.setDrawingListUnderStickyHeader(false);
listView.setOnItemClickListener(new ListClickListener());
+ filterEditText = (EditText) getView().findViewById(R.id.filter);
+ filterEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ cursorFilter = charSequence.toString();
+ getLoaderManager().restartLoader(0, null, PushContactSelectionListFragment.this);
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+
+ }
+ });
+ cursorFilter = null;
}
public void update() {
@@ -138,13 +168,19 @@ public class PushContactSelectionListFragment extends Fragment
@Override
public Loader onCreateLoader(int id, Bundle args) {
- return ContactAccessor.getInstance().getCursorLoaderForContacts(getActivity());
+ if (getActivity().getIntent().getBooleanExtra(PushContactSelectionActivity.PUSH_ONLY_EXTRA, false)) {
+ return ContactAccessor.getInstance().getCursorLoaderForPushContacts(getActivity(), cursorFilter);
+ } else {
+ return ContactAccessor.getInstance().getCursorLoaderForContacts(getActivity(), cursorFilter);
+ }
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
((CursorAdapter) listView.getAdapter()).changeCursor(data);
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
+ if (data != null && data.getCount() < 40) listView.setFastScrollAlwaysVisible(false);
+ else listView.setFastScrollAlwaysVisible(true);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
index 794b7b34dc..8293ffd425 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
@@ -82,8 +82,12 @@ public class ContactAccessor {
null, null, null, ContactsContract.Groups.TITLE + " ASC");
}
- public Loader getCursorLoaderForContacts(Context context) {
- return new ContactsCursorLoader(context);
+ 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) {
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
index c86329d552..11cb8cf9d4 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
@@ -30,6 +30,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
+import android.widget.FilterQueryProvider;
import android.widget.ImageView;
import android.widget.TextView;
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java
index 3ad86b5d10..04dc15e531 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java
@@ -28,22 +28,25 @@ import android.support.v4.content.CursorLoader;
public class ContactsCursorLoader extends CursorLoader {
private final Context context;
+ private final String filter;
+ private final boolean pushOnly;
private ContactsDatabase db;
- public ContactsCursorLoader(Context context) {
+ public ContactsCursorLoader(Context context, String filter, boolean pushOnly) {
super(context);
- this.context = context;
+ this.context = context;
+ this.filter = filter;
+ this.pushOnly = pushOnly;
}
@Override
public Cursor loadInBackground() {
- db = new ContactsDatabase(context);
- return db.getAllContacts();
+ db = ContactsDatabase.getInstance(context);
+ return db.query(filter, pushOnly);
}
@Override
public void onReset() {
super.onReset();
- db.close();
}
}
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java
index 7f1de7252f..322866d3d1 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactsDatabase.java
@@ -20,6 +20,7 @@ import android.content.ContentValues;
import android.content.Context;
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;
@@ -28,10 +29,15 @@ import android.provider.ContactsContract;
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 org.whispersystems.textsecure.util.Util;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
/**
* Database to supply all types of contacts that TextSecure needs to know about
@@ -50,50 +56,102 @@ public class ContactsDatabase {
public static final String NUMBER_COLUMN = ContactsContract.CommonDataKinds.Phone.NUMBER;
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 + " ASC";
private static final String[] ANDROID_PROJECTION = new String[]{ID_COLUMN,
NAME_COLUMN,
NUMBER_TYPE_COLUMN,
NUMBER_COLUMN};
+ private static final String[] CONTACTS_PROJECTION = new String[]{ID_COLUMN,
+ NAME_COLUMN,
+ NUMBER_TYPE_COLUMN,
+ NUMBER_COLUMN,
+ TYPE_COLUMN};
+
public static final int NORMAL_TYPE = 0;
public static final int PUSH_TYPE = 1;
public static final int GROUP_TYPE = 2;
- public ContactsDatabase(Context context) {
+ private static ContactsDatabase instance = null;
+
+ public synchronized static ContactsDatabase getInstance(Context context) {
+ if (instance == null) instance = new ContactsDatabase(context);
+ return instance;
+ }
+
+ public synchronized static void destroyInstance() {
+ if (instance != null) instance.close();
+ instance = null;
+ }
+
+ private ContactsDatabase(Context context) {
this.dbHelper = new DatabaseOpenHelper(context);
- this.context = context;
+ this.context = context;
}
public void close() {
dbHelper.close();
}
- public Cursor getAllContacts() {
- return query(null, null, null);
- }
+ public Cursor query(String filter, boolean pushOnly) {
+ final boolean includeAndroidContacts = !pushOnly && TextSecurePreferences.isSmsNonDataOutEnabled(context);
+ final Cursor localCursor = queryLocalDb(filter);
+ final Cursor androidCursor;
+ final MatrixCursor newNumberCursor;
- private Cursor query(String selection, String[] selectionArgs, String[] columns) {
- final Cursor localCursor = queryLocalDb(selection, selectionArgs, columns);
- final Cursor androidCursor;
+ if (includeAndroidContacts) {
+ androidCursor = queryAndroidDb(filter);
+ } else {
+ androidCursor = null;
+ }
- if (TextSecurePreferences.isSmsNonDataOutEnabled(context)) {
- androidCursor = queryAndroidDb();
- } else{
- return localCursor;
+ if (includeAndroidContacts && !Util.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),
+ 0, filter, NORMAL_TYPE});
+ } else {
+ newNumberCursor = null;
}
- if (localCursor != null && androidCursor != null) return new MergeCursor(new Cursor[]{localCursor,androidCursor});
- else if (localCursor != null) return localCursor;
- else if (androidCursor != null) return androidCursor;
- else return 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() {
- Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, ANDROID_PROJECTION, null, null, CONTACT_LIST_SORT);
+ private Cursor queryAndroidDb(String filter) {
+ final Uri baseUri;
+ if (filter != null) {
+ baseUri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI,
+ Uri.encode(filter));
+ } else {
+ baseUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
+ }
+ Cursor cursor = context.getContentResolver().query(baseUri, ANDROID_PROJECTION, null, null, CONTACT_LIST_SORT);
return new TypedCursorWrapper(cursor);
}
+ private Cursor queryLocalDb(String filter) {
+ final String selection;
+ final String[] selectionArgs;
+ final String fuzzyFilter = "%" + filter + "%";
+ if (!Util.isEmpty(filter)) {
+ selection = FILTER_SELECTION;
+ selectionArgs = new String[]{fuzzyFilter, fuzzyFilter};
+ } else {
+ selection = null;
+ selectionArgs = null;
+ }
+ return queryLocalDb(selection, selectionArgs, null);
+ }
+
private Cursor queryLocalDb(String selection, String[] selectionArgs, String[] columns) {
SQLiteDatabase localDb = dbHelper.getReadableDatabase();
final Cursor localCursor;