diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d57253f3fd..746a8b6dca 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -101,6 +101,10 @@
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/push_contact_selection_activity.xml b/res/layout/push_contact_selection_activity.xml
new file mode 100644
index 0000000000..9836f8c47c
--- /dev/null
+++ b/res/layout/push_contact_selection_activity.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/push_contact_selection_list_activity.xml b/res/layout/push_contact_selection_list_activity.xml
new file mode 100644
index 0000000000..f25dd83a65
--- /dev/null
+++ b/res/layout/push_contact_selection_list_activity.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/push_contact_selection_list_item.xml b/res/layout/push_contact_selection_list_item.xml
new file mode 100644
index 0000000000..928d599f5e
--- /dev/null
+++ b/res/layout/push_contact_selection_list_item.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/selected_recipient_list_item.xml b/res/layout/selected_recipient_list_item.xml
new file mode 100644
index 0000000000..4ef65831f9
--- /dev/null
+++ b/res/layout/selected_recipient_list_item.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/group_create.xml b/res/menu/group_create.xml
new file mode 100644
index 0000000000..b9a28fedb0
--- /dev/null
+++ b/res/menu/group_create.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/res/menu/text_secure_normal.xml b/res/menu/text_secure_normal.xml
index 1150efb128..1cc156505e 100644
--- a/res/menu/text_secure_normal.xml
+++ b/res/menu/text_secure_normal.xml
@@ -5,6 +5,10 @@
android:icon="?attr/menu_new_conversation_icon"
android:showAsAction="always" />
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index d562942f94..e9b232c2ab 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -33,6 +33,7 @@
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c467194aec..abbef3a19e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -370,7 +370,8 @@
No contacts.
-
+ Finding contacts...
+
Select for
diff --git a/res/values/themes.xml b/res/values/themes.xml
index a8874d72a6..3224caf01b 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -27,6 +27,7 @@
- @drawable/ic_ime_dark
- @drawable/ic_action_new_holo_light
+ - @drawable/ic_action_add_group_holo_light
- @drawable/ic_menu_search_holo_light
- @drawable/ic_menu_call_holo_light
- @drawable/ic_menu_unlock_holo_light
@@ -75,6 +76,7 @@
- @drawable/ic_ime_light
- @drawable/ic_action_new_holo_dark
+ - @drawable/ic_action_add_group_holo_dark
- @drawable/ic_menu_search_holo_dark
- @drawable/ic_menu_call_holo_dark
- @drawable/ic_menu_unlock_holo_dark
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
index 834b06c1f3..e131252dca 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
@@ -19,7 +19,6 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index a1dd2adb27..8c28c56ccc 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -659,7 +659,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
}
-
private void initializeReceivers() {
securityUpdateReceiver = new BroadcastReceiver() {
@Override
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index c4e608cb9b..3e0103b180 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -140,6 +140,7 @@ public class ConversationFragment extends SherlockListFragment
long dateReceived = message.getDateReceived();
long dateSent = message.getDateSent();
+
SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d, yyyy 'at' hh:mm:ss a zzz");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_message_details);
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 4661b0bdb5..d0f0335378 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -139,6 +139,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
switch (item.getItemId()) {
case R.id.menu_new_message: createConversation(-1, null, defaultType); return true;
+ case R.id.menu_new_group: createGroup(); return true;
case R.id.menu_settings: handleDisplaySettings(); return true;
case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
@@ -153,6 +154,11 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
createConversation(threadId, recipients, distributionType);
}
+ private void createGroup() {
+ Intent intent = new Intent(this, GroupCreateActivity.class);
+ startActivity(intent);
+ }
+
private void createConversation(long threadId, Recipients recipients, int distributionType) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients);
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
new file mode 100644
index 0000000000..61fafba25c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -0,0 +1,151 @@
+package org.thoughtcrime.securesms;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListView;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.ActionBarUtil;
+import org.thoughtcrime.securesms.util.DynamicLanguage;
+import org.thoughtcrime.securesms.util.DynamicTheme;
+import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+
+public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActivity {
+
+ private final DynamicTheme dynamicTheme = new DynamicTheme();
+ private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
+
+ private static final int PICK_CONTACT = 1;
+ private static final int SELECT_PHOTO = 100;
+ private ListView lv;
+
+ private Set selectedContacts;
+
+ @Override
+ public void onCreate(Bundle state) {
+ dynamicTheme.onCreate(this);
+ dynamicLanguage.onCreate(this);
+ super.onCreate(state);
+
+ setContentView(R.layout.group_create_activity);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ ActionBarUtil.initializeDefaultActionBar(this, getSupportActionBar(), "New Group");
+
+ selectedContacts = new HashSet();
+ initializeResources();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ dynamicTheme.onResume(this);
+ }
+
+ private void initializeResources() {
+ lv = (ListView) findViewById(R.id.selected_contacts_list);
+ lv.setAdapter(new SelectedRecipientsAdapter(this, android.R.id.text1, new ArrayList()));
+ (findViewById(R.id.add_people_button)).setOnClickListener(new AddRecipientButtonListener());
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuInflater inflater = this.getSupportMenuInflater();
+ menu.clear();
+
+ inflater.inflate(R.menu.group_create, menu);
+ super.onPrepareOptionsMenu(menu);
+ return true;
+ }
+
+ private List selectedContactsAsIdArray() {
+ final List ids = new ArrayList();
+ for (Recipient recipient : selectedContacts) {
+ ids.add(String.valueOf(recipient.getCanonicalAddress(this)));
+ }
+ return ids;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ super.onOptionsItemSelected(item);
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ case R.id.menu_create_group:
+ finish(); // TODO not this
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onActivityResult(int reqCode, int resultCode, Intent data) {
+ Log.w("ComposeMessageActivity", "onActivityResult called: " + resultCode + " , " + data);
+ super.onActivityResult(reqCode, resultCode, data);
+
+ if (data == null || resultCode != Activity.RESULT_OK)
+ return;
+
+ switch (reqCode) {
+ case PICK_CONTACT:
+ Recipients recipients = data.getParcelableExtra("recipients");
+ for (Recipient recipient : recipients.getRecipientsList()) {
+ if (!selectedContacts.contains(recipient)) {
+ Log.w("poop", "contains that shit.");
+ selectedContacts.add(recipient);
+ }
+ }
+ SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter)lv.getAdapter();
+ adapter.clear();
+ Iterator selectedContactsIter = selectedContacts.iterator();
+ while (selectedContactsIter.hasNext()) {
+ adapter.add(selectedContactsIter.next());
+ }
+ break;
+ case SELECT_PHOTO:
+ if(resultCode == RESULT_OK){
+ Uri selectedImage = data.getData();
+ String[] filePathColumn = {MediaStore.Images.Media.DATA};
+
+ Cursor cursor = getContentResolver().query(
+ selectedImage, filePathColumn, null, null, null);
+ cursor.moveToFirst();
+
+ int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
+ String filePath = cursor.getString(columnIndex);
+ cursor.close();
+
+ Bitmap selectedBitmap = BitmapFactory.decodeFile(filePath);
+ break;
+ }
+ }
+ }
+
+ private class AddRecipientButtonListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
+ startActivityForResult(intent, PICK_CONTACT);
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java b/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java
new file mode 100644
index 0000000000..65f84f2075
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 2011 Whisper Systems
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.ActionBarUtil;
+import org.thoughtcrime.securesms.util.DynamicTheme;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+
+/**
+ * Activity container for selecting a list of contacts. Provides a tab frame for
+ * contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity
+ * when selecting a list of contacts to address a message to.
+ *
+ * @author Moxie Marlinspike
+ *
+ */
+public class PushContactSelectionActivity extends PassphraseRequiredSherlockFragmentActivity {
+
+ private final DynamicTheme dynamicTheme = new DynamicTheme();
+
+ private Recipients recipients;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ dynamicTheme.onCreate(this);
+ super.onCreate(icicle);
+
+ final ActionBar actionBar = this.getSupportActionBar();
+ ActionBarUtil.initializeDefaultActionBar(this, actionBar);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.push_contact_selection_activity);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ dynamicTheme.onResume(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = this.getSupportMenuInflater();
+ inflater.inflate(R.menu.contact_selection, menu);
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_selection_finished:
+ case android.R.id.home:
+ handleSelectionFinished(); return true;
+ }
+
+ return false;
+ }
+
+ private void handleSelectionFinished() {
+ PushContactSelectionListFragment contactsFragment = (PushContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
+ recipients = contactsFragment.getSelectedContacts();
+
+ Intent resultIntent = getIntent();
+ resultIntent.putExtra("recipients", this.recipients);
+
+ setResult(RESULT_OK, resultIntent);
+
+ finish();
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java
new file mode 100644
index 0000000000..f666125f45
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java
@@ -0,0 +1,336 @@
+/**
+ * Copyright (C) 2011 Whisper Systems
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+
+import org.thoughtcrime.securesms.contacts.ContactAccessor;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
+import org.thoughtcrime.securesms.contacts.PushFilterCursorWrapper;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Activity for selecting a list of contacts. Displayed inside
+ * a PushContactSelectionActivity tab frame, and ultimately called by
+ * ComposeMessageActivity for selecting a list of destination contacts.
+ *
+ * @author Moxie Marlinspike
+ *
+ */
+
+public class PushContactSelectionListFragment extends SherlockListFragment
+ implements LoaderManager.LoaderCallbacks
+{
+
+ private final HashMap selectedContacts = new HashMap();
+ private static LayoutInflater li;
+
+ @Override
+ public void onActivityCreated(Bundle icicle) {
+ super.onCreate(icicle);
+ li = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ initializeResources();
+ initializeCursor();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.push_contact_selection_list_activity, container, false);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.contact_selection_list, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+
+ switch (item.getItemId()) {
+ case R.id.menu_select_all: handleSelectAll(); return true;
+ case R.id.menu_unselect_all: handleUnselectAll(); return true;
+ }
+
+ super.onOptionsItemSelected(item);
+ return false;
+ }
+
+ public Recipients getSelectedContacts() {
+ List recipientList = new LinkedList();
+
+ for (ContactData contactData : selectedContacts.values()) {
+ for (NumberData numberData : contactData.numbers) {
+ recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
+ }
+ }
+
+ return new Recipients(recipientList);
+ }
+
+
+ private void handleUnselectAll() {
+ selectedContacts.clear();
+ ((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
+ }
+
+ private void handleSelectAll() {
+ selectedContacts.clear();
+
+ Cursor cursor = null;
+
+ try {
+ cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(getActivity());
+
+ while (cursor != null && cursor.moveToNext()) {
+ ContactData contactData = ContactAccessor.getInstance().getContactData(getActivity(), cursor);
+
+ if (contactData.numbers.isEmpty()) continue;
+ else if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
+ else addMultipleNumberContact(contactData, null, null);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ ((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
+ }
+
+ private void addSingleNumberContact(ContactData contactData) {
+ selectedContacts.put(contactData.id, contactData);
+ }
+
+ private void removeContact(ContactData contactData) {
+ selectedContacts.remove(contactData.id);
+ }
+
+ private void addMultipleNumberContact(ContactData contactData, TextView textView, CheckBox checkBox) {
+ String[] options = new String[contactData.numbers.size()];
+ int i = 0;
+
+ for (NumberData option : contactData.numbers) {
+ options[i++] = option.type + " " + option.number;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.ContactSelectionlistFragment_select_for + " " + contactData.name);
+ builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData));
+ builder.setPositiveButton(android.R.string.ok, new DiscriminatorFinishedListener(contactData, textView, checkBox));
+ builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView, checkBox));
+ builder.show();
+ }
+
+ private void initializeCursor() {
+ setListAdapter(new ContactSelectionListAdapter(getActivity(), null));
+ this.getLoaderManager().initLoader(0, null, this);
+ }
+
+ private void initializeResources() {
+ this.getListView().setFocusable(true);
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ ((ContactItemView)v).selected();
+ }
+
+ private class ContactSelectionListAdapter extends CursorAdapter {
+
+ public ContactSelectionListAdapter(Context context, Cursor c) {
+ super(context, c);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ ContactItemView view = new ContactItemView(context);
+ bindView(view, context, cursor);
+
+ return view;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ PushFilterCursorWrapper wrappedCursor = (PushFilterCursorWrapper) cursor;
+ boolean isPushUser = wrappedCursor.getPushCount() > wrappedCursor.getPosition();
+ ContactData contactData = ContactAccessor.getInstance().getContactData(context, cursor);
+ ((ContactItemView)view).set(contactData, isPushUser);
+ }
+ }
+
+ private class ContactItemView extends RelativeLayout {
+ private ContactData contactData;
+ private CheckBox checkBox;
+ private TextView name;
+ private TextView number;
+ private TextView label;
+
+ public ContactItemView(Context context) {
+ super(context);
+
+ li.inflate(R.layout.push_contact_selection_list_item, this, true);
+
+ this.name = (TextView) findViewById(R.id.name);
+ this.number = (TextView) findViewById(R.id.number);
+ this.label = (TextView) findViewById(R.id.label);
+ this.checkBox = (CheckBox) findViewById(R.id.check_box);
+ }
+
+ public void selected() {
+
+ checkBox.toggle();
+
+ if (checkBox.isChecked()) {
+ if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
+ else addMultipleNumberContact(contactData, name, checkBox);
+ } else {
+ removeContact(contactData);
+ }
+ }
+
+ public void set(ContactData contactData, boolean isPushUser) {
+ this.contactData = contactData;
+
+ if (!isPushUser) {
+ this.name.setTextColor(0xa0000000);
+ this.number.setTextColor(0xa0000000);
+ this.checkBox.setVisibility(View.GONE);
+ } else {
+ this.name.setTextColor(0xff000000);
+ this.number.setTextColor(0xff000000);
+ this.checkBox.setVisibility(View.VISIBLE);
+ }
+
+ if (selectedContacts.containsKey(contactData.id))
+ this.checkBox.setChecked(true);
+ else
+ this.checkBox.setChecked(false);
+
+ this.name.setText(contactData.name);
+
+ if (contactData.numbers.isEmpty()) {
+ this.name.setEnabled(false);
+ this.number.setText("");
+ this.label.setText("");
+ } else {
+ this.number.setText(contactData.numbers.get(0).number);
+ this.label.setText(contactData.numbers.get(0).type);
+ }
+ }
+ }
+
+ private class DiscriminatorFinishedListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ private final ContactData contactData;
+ private final TextView textView;
+ private final CheckBox checkBox;
+
+ public DiscriminatorFinishedListener(ContactData contactData, TextView textView, CheckBox checkBox) {
+ this.contactData = contactData;
+ this.textView = textView;
+ this.checkBox = checkBox;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ ContactData selected = selectedContacts.get(contactData.id);
+
+ if (selected == null && textView != null) {
+ if (textView != null) checkBox.setChecked(false);
+ } else if (selected.numbers.size() == 0) {
+ selectedContacts.remove(selected.id);
+ if (textView != null) checkBox.setChecked(false);
+ }
+
+ if (textView == null)
+ ((CursorAdapter) getListView().getAdapter()).notifyDataSetChanged();
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ onClick(dialog, 0);
+ }
+ }
+
+ private class DiscriminatorClickedListener implements DialogInterface.OnMultiChoiceClickListener {
+ private final ContactData contactData;
+
+ public DiscriminatorClickedListener(ContactData contactData) {
+ this.contactData = contactData;
+ }
+
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ Log.w("ContactSelectionListActivity", "Got checked: " + isChecked);
+
+ ContactData existing = selectedContacts.get(contactData.id);
+
+ if (existing == null) {
+ Log.w("ContactSelectionListActivity", "No existing contact data, creating...");
+
+ if (!isChecked)
+ throw new AssertionError("We shouldn't be unchecking data that doesn't exist.");
+
+ existing = new ContactData(contactData.id, contactData.name);
+ selectedContacts.put(existing.id, existing);
+ }
+
+ NumberData selectedData = contactData.numbers.get(which);
+
+ if (!isChecked) existing.numbers.remove(selectedData);
+ else existing.numbers.add(selectedData);
+ }
+ }
+
+ @Override
+ public Loader onCreateLoader(int arg0, Bundle arg1) {
+ return ContactAccessor.getInstance().getCursorLoaderForContactsWithNumbers(getActivity());
+ }
+
+ @Override
+ public void onLoadFinished(Loader arg0, Cursor cursor) {
+ ((CursorAdapter) getListAdapter()).changeCursor(new PushFilterCursorWrapper(cursor, getActivity()));
+ }
+
+ @Override
+ public void onLoaderReset(Loader arg0) {
+ ((CursorAdapter) getListAdapter()).changeCursor(null);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java b/src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java
new file mode 100644
index 0000000000..6725eb4da2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java
@@ -0,0 +1,106 @@
+package org.thoughtcrime.securesms.contacts;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.os.Debug;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.util.Util;
+import org.whispersystems.textsecure.util.InvalidNumberException;
+
+import java.util.List;
+
+public class PushFilterCursorWrapper extends CursorWrapper {
+ private int[] pushIndex;
+ private int[] normalIndex;
+ private int count = 0;
+ private int pushCount = 0;
+ private int pos = 0;
+
+ private final ContactAccessor contactAccessor = ContactAccessor.getInstance();
+
+ /*
+ * Don't know of a better way to do this without a large filtering through the entire dataset at first
+ */
+ public PushFilterCursorWrapper(Cursor cursor, Context context) {
+ super(cursor);
+ this.count = super.getCount();
+ this.pushIndex = new int[this.count];
+ this.normalIndex = new int[this.count];
+ int pushPos = 0;
+ int normalPos = 0;
+ for (int i = 0; i < this.count; i++) {
+ super.moveToPosition(i);
+
+
+ List numbers = contactAccessor.getContactData(context, cursor).numbers;
+ if (numbers.size() > 0) {
+ try {
+ if (Util.isPushTransport(context, Util.canonicalizeNumber(context, numbers.get(0).number)))
+ this.pushIndex[pushPos++] = i;
+ else
+ this.normalIndex[normalPos++] = i;
+ } catch (InvalidNumberException ine) {
+ }
+ }
+ }
+ this.pushCount = pushPos;
+ super.moveToFirst();
+ }
+
+ @Override
+ public boolean move(int offset) {
+ return this.moveToPosition(this.pos + offset);
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return this.moveToPosition(this.pos + 1);
+ }
+
+ @Override
+ public boolean moveToPrevious() {
+ return this.moveToPosition(this.pos - 1);
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ return this.moveToPosition(0);
+ }
+
+ @Override
+ public boolean moveToLast() {
+ return this.moveToPosition(this.count - 1);
+ }
+
+ private int getPostFilteredPosition(int preFilteredPosition) {
+ return preFilteredPosition < this.pushCount
+ ? this.pushIndex[preFilteredPosition]
+ : this.normalIndex[preFilteredPosition - pushCount];
+ }
+
+ @Override
+ public boolean moveToPosition(int position) {
+ if (position >= this.count || position < 0)
+ return false;
+ pos = position;
+ return super.moveToPosition(getPostFilteredPosition(position));
+ }
+
+ @Override
+ public int getCount() {
+ return this.count;
+ }
+
+ public int getPushCount() {
+ return this.pushCount;
+ }
+
+ @Override
+ public int getPosition() {
+ return this.pos;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index 9715f429f0..f420f947cf 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -24,15 +24,25 @@ import android.os.Parcelable;
import android.util.Log;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
+import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
+import org.thoughtcrime.securesms.util.Util;
+import org.whispersystems.textsecure.directory.Directory;
+import org.whispersystems.textsecure.directory.NotInDirectoryException;
+import org.whispersystems.textsecure.push.ContactTokenDetails;
+import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.FutureTaskListener;
+import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.ListenableFutureTask;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
+import java.io.IOException;
import java.util.HashSet;
public class Recipient implements Parcelable, CanonicalRecipientAddress {
+ private final static String TAG = "Recipient";
+
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public Recipient createFromParcel(Parcel in) {
return new Recipient(in);
@@ -152,7 +162,31 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(getNumber());
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || ((Object) this).getClass() != o.getClass()) return false; // the Object casting is due to an Android Studio bug...
+
+ Recipient recipient = (Recipient) o;
+
+ if (contactUri != null ? !contactUri.equals(recipient.contactUri) : recipient.contactUri != null)
+ return false;
+ if (name != null ? !name.equals(recipient.name) : recipient.name != null) return false;
+ if (number != null ? !number.equals(recipient.number) : recipient.number != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = number != null ? number.hashCode() : 0;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + (contactUri != null ? contactUri.hashCode() : 0);
+ return result;
+ }
+
public static interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}
+
}
diff --git a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java
new file mode 100644
index 0000000000..5b64589808
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java
@@ -0,0 +1,72 @@
+package org.thoughtcrime.securesms.util;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.recipients.Recipient;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SelectedRecipientsAdapter extends ArrayAdapter {
+
+ private ArrayList recipients;
+
+ public SelectedRecipientsAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+ }
+
+ public SelectedRecipientsAdapter(Context context, int resource, ArrayList recipients) {
+ super(context, resource, recipients);
+ this.recipients = recipients;
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+
+ View v = convertView;
+
+ if (v == null) {
+
+ LayoutInflater vi;
+ vi = LayoutInflater.from(getContext());
+ v = vi.inflate(R.layout.selected_recipient_list_item, null);
+
+ }
+
+ Recipient p = getItem(position);
+
+ if (p != null) {
+
+ TextView name = (TextView) v.findViewById(R.id.name);
+ TextView phone = (TextView) v.findViewById(R.id.phone);
+ ImageButton delete = (ImageButton) v.findViewById(R.id.delete);
+
+ if (name != null) {
+ name.setText(p.getName());
+ }
+ if (phone != null) {
+
+ phone.setText(p.getNumber());
+ }
+ if (delete != null) {
+ delete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ recipients.remove(position);
+ SelectedRecipientsAdapter.this.notifyDataSetChanged();
+ }
+ });
+ }
+ }
+
+ return v;
+
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java
index 03e8626b49..ae09e985fb 100644
--- a/src/org/thoughtcrime/securesms/util/Util.java
+++ b/src/org/thoughtcrime/securesms/util/Util.java
@@ -28,6 +28,11 @@ import android.os.Build;
import android.provider.Telephony;
import org.thoughtcrime.securesms.mms.MmsRadio;
+import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
+import org.whispersystems.textsecure.directory.Directory;
+import org.whispersystems.textsecure.directory.NotInDirectoryException;
+import org.whispersystems.textsecure.push.ContactTokenDetails;
+import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
@@ -151,6 +156,31 @@ public class Util {
(context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context)));
}
+ public static boolean isPushTransport(Context context, String destination) {
+ Directory directory = Directory.getInstance(context);
+
+ try {
+ return directory.isActiveNumber(destination);
+ } catch (NotInDirectoryException e) {
+ try {
+ PushServiceSocket socket = PushServiceSocketFactory.create(context);
+ String contactToken = directory.getToken(destination);
+ ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken);
+
+ if (registeredUser == null) {
+ registeredUser = new ContactTokenDetails(contactToken);
+ directory.setToken(registeredUser, false);
+ return false;
+ } else {
+ directory.setToken(registeredUser, true);
+ return true;
+ }
+ } catch (IOException e1) {
+ Log.w("UniversalTransport", e1);
+ return false;
+ }
+ }
+ }
// public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
// return BitmapFactory.decodeStream(src);
//// BitmapFactory.Options options = new BitmapFactory.Options();