diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a5db8bb207..e41c4179b8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -39,6 +39,11 @@
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"/>
+
+
+
@@ -51,11 +56,6 @@
-
-
-
-
-
diff --git a/res/drawable-hdpi/hairline_left.9.png b/res/drawable-hdpi/hairline_left.9.png
new file mode 100644
index 0000000000..5d9c7210ea
Binary files /dev/null and b/res/drawable-hdpi/hairline_left.9.png differ
diff --git a/res/drawable-hdpi/hairline_right.9.png b/res/drawable-hdpi/hairline_right.9.png
new file mode 100644
index 0000000000..610d341204
Binary files /dev/null and b/res/drawable-hdpi/hairline_right.9.png differ
diff --git a/res/drawable-hdpi/ic_menu_done_holo_dark.png b/res/drawable-hdpi/ic_menu_done_holo_dark.png
new file mode 100644
index 0000000000..750c77ef93
Binary files /dev/null and b/res/drawable-hdpi/ic_menu_done_holo_dark.png differ
diff --git a/res/drawable-hdpi/ic_tab_contacts.png b/res/drawable-hdpi/ic_tab_contacts.png
new file mode 100644
index 0000000000..e5deb01129
Binary files /dev/null and b/res/drawable-hdpi/ic_tab_contacts.png differ
diff --git a/res/drawable-hdpi/ic_tab_groups.png b/res/drawable-hdpi/ic_tab_groups.png
new file mode 100644
index 0000000000..32f419ce67
Binary files /dev/null and b/res/drawable-hdpi/ic_tab_groups.png differ
diff --git a/res/drawable-hdpi/ic_tab_recent.png b/res/drawable-hdpi/ic_tab_recent.png
new file mode 100644
index 0000000000..1edab8db09
Binary files /dev/null and b/res/drawable-hdpi/ic_tab_recent.png differ
diff --git a/res/drawable-mdpi/hairline_left.9.png b/res/drawable-mdpi/hairline_left.9.png
new file mode 100644
index 0000000000..1fd051d9b4
Binary files /dev/null and b/res/drawable-mdpi/hairline_left.9.png differ
diff --git a/res/drawable-mdpi/hairline_right.9.png b/res/drawable-mdpi/hairline_right.9.png
new file mode 100644
index 0000000000..75f84a43e8
Binary files /dev/null and b/res/drawable-mdpi/hairline_right.9.png differ
diff --git a/res/drawable-mdpi/ic_menu_done_holo_dark.png b/res/drawable-mdpi/ic_menu_done_holo_dark.png
new file mode 100644
index 0000000000..7ec8c8f5cd
Binary files /dev/null and b/res/drawable-mdpi/ic_menu_done_holo_dark.png differ
diff --git a/res/drawable-mdpi/ic_tab_contacts.png b/res/drawable-mdpi/ic_tab_contacts.png
new file mode 100644
index 0000000000..d08b94a4b0
Binary files /dev/null and b/res/drawable-mdpi/ic_tab_contacts.png differ
diff --git a/res/drawable-mdpi/ic_tab_groups.png b/res/drawable-mdpi/ic_tab_groups.png
new file mode 100644
index 0000000000..c3cf14a174
Binary files /dev/null and b/res/drawable-mdpi/ic_tab_groups.png differ
diff --git a/res/drawable-mdpi/ic_tab_recent.png b/res/drawable-mdpi/ic_tab_recent.png
new file mode 100644
index 0000000000..85bf5f38cb
Binary files /dev/null and b/res/drawable-mdpi/ic_tab_recent.png differ
diff --git a/res/drawable-xhdpi/hairline_left.9.png b/res/drawable-xhdpi/hairline_left.9.png
new file mode 100644
index 0000000000..d2574bc98f
Binary files /dev/null and b/res/drawable-xhdpi/hairline_left.9.png differ
diff --git a/res/drawable-xhdpi/hairline_right.9.png b/res/drawable-xhdpi/hairline_right.9.png
new file mode 100644
index 0000000000..bb7948566d
Binary files /dev/null and b/res/drawable-xhdpi/hairline_right.9.png differ
diff --git a/res/drawable-xhdpi/ic_menu_done_holo_dark.png b/res/drawable-xhdpi/ic_menu_done_holo_dark.png
new file mode 100644
index 0000000000..b9af04a859
Binary files /dev/null and b/res/drawable-xhdpi/ic_menu_done_holo_dark.png differ
diff --git a/res/drawable-xhdpi/ic_tab_contacts.png b/res/drawable-xhdpi/ic_tab_contacts.png
new file mode 100644
index 0000000000..dc4c390f0d
Binary files /dev/null and b/res/drawable-xhdpi/ic_tab_contacts.png differ
diff --git a/res/drawable-xhdpi/ic_tab_groups.png b/res/drawable-xhdpi/ic_tab_groups.png
new file mode 100644
index 0000000000..d7348fb9ac
Binary files /dev/null and b/res/drawable-xhdpi/ic_tab_groups.png differ
diff --git a/res/drawable-xhdpi/ic_tab_recent.png b/res/drawable-xhdpi/ic_tab_recent.png
new file mode 100644
index 0000000000..98cc1af35a
Binary files /dev/null and b/res/drawable-xhdpi/ic_tab_recent.png differ
diff --git a/res/layout/contact_selection_activity.xml b/res/layout/contact_selection_activity.xml
index 242821b730..6810629dca 100644
--- a/res/layout/contact_selection_activity.xml
+++ b/res/layout/contact_selection_activity.xml
@@ -1,42 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/res/menu/contact_selection.xml b/res/menu/contact_selection.xml
new file mode 100644
index 0000000000..6f3145eb20
--- /dev/null
+++ b/res/menu/contact_selection.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/res/menu/contact_selection_list.xml b/res/menu/contact_selection_list.xml
new file mode 100644
index 0000000000..95ffa978f6
--- /dev/null
+++ b/res/menu/contact_selection_list.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
index 40d9e57069..b486a5fd8d 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
@@ -1,6 +1,6 @@
-/**
+/**
* 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
@@ -10,54 +10,66 @@
* 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 org.thoughtcrime.securesms.service.KeyCachingService;
-
-import android.app.TabActivity;
import android.content.Intent;
import android.os.Bundle;
-import android.view.Window;
-import android.widget.TabHost;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.ActionBar.Tab;
+import com.actionbarsherlock.app.ActionBar.TabListener;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.service.KeyCachingService;
/**
* 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 ContactSelectionActivity extends SherlockFragmentActivity {
-public class ContactSelectionActivity extends TabActivity implements TabHost.OnTabChangeListener {
+ private ContactSelectionListFragment contactsFragment;
+ private ContactSelectionGroupsFragment groupsFragment;
+ private ContactSelectionRecentFragment recentFragment;
- private TabHost tabHost;
+ private Recipients recipients;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.contact_selection_activity);
+ ActionBar actionBar = this.getSupportActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ actionBar.setDisplayHomeAsUpEnabled(true);
- tabHost = getTabHost();
- tabHost.setOnTabChangedListener(this);
+ setContentView(R.layout.contact_selection_activity);
setupContactsTab();
setupGroupsTab();
setupRecentTab();
}
-
+
@Override
protected void onStart() {
super.onStart();
registerPassphraseActivityStarted();
}
-
+
@Override
protected void onStop() {
super.onStop();
@@ -65,48 +77,102 @@ public class ContactSelectionActivity extends TabActivity implements TabHost.OnT
}
@Override
- public boolean onSearchRequested() {
+ 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() {
+ recipients = contactsFragment.getSelectedContacts();
+ recipients.append(recentFragment.getSelectedContacts());
+ recipients.append(groupsFragment.getSelectedContacts());
+
+ Intent resultIntent = getIntent();
+ resultIntent.putExtra("recipients", this.recipients);
+
+ setResult(RESULT_OK, resultIntent);
+
+ finish();
+ }
+
+ private ActionBar.Tab constructTab(final Fragment fragment) {
+ ActionBar actionBar = this.getSupportActionBar();
+ ActionBar.Tab tab = actionBar.newTab();
+
+ tab.setTabListener(new TabListener(){
+ @Override
+ public void onTabSelected(Tab tab, FragmentTransaction ignore) {
+ FragmentManager manager = ContactSelectionActivity.this.getSupportFragmentManager();
+ FragmentTransaction ft = manager.beginTransaction();
+
+ ft.add(R.id.fragment_container, fragment);
+ ft.commit();
+ }
+
+ @Override
+ public void onTabUnselected(Tab tab, FragmentTransaction ignore) {
+ FragmentManager manager = ContactSelectionActivity.this.getSupportFragmentManager();
+ FragmentTransaction ft = manager.beginTransaction();
+ ft.remove(fragment);
+ ft.commit();
+ }
+ @Override
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {}
+ });
+
+ return tab;
+ }
+
+ private void setupContactsTab() {
+ contactsFragment = (ContactSelectionListFragment)Fragment.instantiate(this,
+ ContactSelectionListFragment.class.getName());
+ ActionBar.Tab contactsTab = constructTab(contactsFragment);
+ contactsTab.setIcon(R.drawable.ic_tab_contacts);
+ this.getSupportActionBar().addTab(contactsTab);
+ }
+
+ private void setupGroupsTab() {
+ groupsFragment = (ContactSelectionGroupsFragment)Fragment.instantiate(this,
+ ContactSelectionGroupsFragment.class.getName());
+ ActionBar.Tab groupsTab = constructTab(groupsFragment);
+ groupsTab.setIcon(R.drawable.ic_tab_groups);
+ this.getSupportActionBar().addTab(groupsTab);
+ }
+
+ private void setupRecentTab() {
+ recentFragment = (ContactSelectionRecentFragment)Fragment.instantiate(this,
+ ContactSelectionRecentFragment.class.getName());
+
+ ActionBar.Tab recentTab = constructTab(recentFragment);
+ recentTab.setIcon(R.drawable.ic_tab_recent);
+ this.getSupportActionBar().addTab(recentTab);
+ }
+
private void registerPassphraseActivityStarted() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
- startService(intent);
+ startService(intent);
}
-
+
private void registerPassphraseActivityStopped() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
startService(intent);
}
-
- private void setupGroupsTab() {
- Intent intent = new Intent(this, GroupSelectionListActivity.class);
-
- tabHost.addTab(tabHost.newTabSpec("groups")
- .setIndicator("Groups", getResources().getDrawable(android.R.drawable.ic_menu_share))
- .setContent(intent));
- }
-
- private void setupRecentTab() {
- Intent intent = new Intent(this, ContactSelectionRecentActivity.class);
-
- tabHost.addTab(tabHost.newTabSpec("recent")
- .setIndicator("Recent", getResources().getDrawable(android.R.drawable.ic_menu_call))
- .setContent(intent));
- }
-
- private void setupContactsTab() {
- Intent intent = new Intent(this, ContactSelectionListActivity.class);
- tabHost.addTab(tabHost.newTabSpec("contacts")
- .setIndicator("Contacts", getResources().getDrawable(android.R.drawable.ic_menu_agenda))
- .setContent(intent));
- }
- public void onTabChanged(String tabId) {
-
- }
-
+
}
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java
new file mode 100644
index 0000000000..94be129ac9
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java
@@ -0,0 +1,231 @@
+/**
+ * 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.Context;
+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.CheckedTextView;
+import android.widget.CursorAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+import org.thoughtcrime.securesms.contacts.ContactAccessor;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.GroupData;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * An activity for selecting a list of "contact groups." Displayed
+ * by ContactSelectionActivity in a tabbed frame, and ultimately called
+ * by ComposeMessageActivity for selecting a list of recipients.
+ *
+ * @author Moxie Marlinspike
+ *
+ */
+public class ContactSelectionGroupsFragment extends SherlockListFragment
+ implements LoaderManager.LoaderCallbacks
+{
+
+ private final HashMap selectedGroups = new HashMap();
+
+ @Override
+ public void onActivityCreated(Bundle icicle) {
+ super.onActivityCreated(icicle);
+
+ initializeResources();
+ initializeCursor();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.contact_selection_group_activity, container, false);
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ ((GroupItemView)v).selected();
+ }
+
+ private void initializeCursor() {
+ setListAdapter(new GroupSelectionListAdapter(getActivity(), null));
+ this.getLoaderManager().initLoader(0, null, this);
+ }
+
+ private void initializeResources() {
+ this.getListView().setFocusable(true);
+ }
+
+ public Recipients getSelectedContacts() {
+ List recipientList = new LinkedList();
+
+ for (GroupData groupData : selectedGroups.values()) {
+ List contactDataList = ContactAccessor.getInstance()
+ .getGroupMembership(getActivity(), groupData.id);
+
+ Log.w("GroupSelectionListActivity", "Got contacts in group: " + contactDataList.size());
+
+ for (ContactData contactData : contactDataList) {
+ for (NumberData numberData : contactData.numbers) {
+ recipientList.add(new Recipient(contactData.name, numberData.number, null));
+ }
+ }
+ }
+
+ return new Recipients(recipientList);
+ }
+
+ private void addGroup(GroupData groupData) {
+ selectedGroups.put(groupData.id, groupData);
+ }
+
+ private void removeGroup(GroupData groupData) {
+ selectedGroups.remove(groupData.id);
+ }
+
+ private class GroupSelectionListAdapter extends CursorAdapter {
+
+ public GroupSelectionListAdapter(Context context, Cursor cursor) {
+ super(context, cursor);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ GroupItemView view = new GroupItemView(context);
+ bindView(view, context, cursor);
+ return view;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ GroupData groupData = ContactAccessor.getInstance().getGroupData(getActivity(), cursor);
+ ((GroupItemView)view).set(groupData);
+ }
+ }
+
+ private class GroupItemView extends LinearLayout {
+ private GroupData groupData;
+ private CheckedTextView name;
+
+ public GroupItemView(Context context) {
+ super(context);
+
+ LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ li.inflate(R.layout.contact_selection_group_item, this, true);
+
+ this.name = (CheckedTextView)findViewById(R.id.name);
+ }
+
+ public void selected() {
+ name.toggle();
+
+ if (name.isChecked()) {
+ addGroup(groupData);
+ } else {
+ removeGroup(groupData);
+ }
+ }
+
+ public void set(GroupData groupData) {
+ this.groupData = groupData;
+
+ if (selectedGroups.containsKey(groupData.id))
+ this.name.setChecked(true);
+ else
+ this.name.setChecked(false);
+
+ this.name.setText(groupData.name);
+ }
+ }
+
+// private class GroupAggregationHandler extends Handler implements Runnable {
+// private List recipientList = new LinkedList();
+// private ProgressDialog progressDialog;
+// private final Context context;
+//
+// public GroupAggregationHandler(Context context) {
+// this.context = context;
+// }
+//
+// public void run() {
+// recipientList.clear();
+//
+// for (GroupData groupData : selectedGroups.values()) {
+// List contactDataList = ContactAccessor.getInstance()
+// .getGroupMembership(getActivity(), groupData.id);
+//
+// Log.w("GroupSelectionListActivity", "Got contacts in group: " + contactDataList.size());
+//
+// for (ContactData contactData : contactDataList) {
+// for (NumberData numberData : contactData.numbers) {
+// recipientList.add(new Recipient(contactData.name, numberData.number, null));
+// }
+// }
+// }
+//
+// this.obtainMessage().sendToTarget();
+// }
+//
+// public void aggregateContacts() {
+// progressDialog = new ProgressDialog(context);
+// progressDialog.setTitle("Aggregating Contacts");
+// progressDialog.setMessage("Aggregating group contacts...");
+// progressDialog.setCancelable(false);
+// progressDialog.setIndeterminate(true);
+// progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+// progressDialog.show();
+// Log.w("GroupSelectionListActivity", "Showing group spinner...");
+// new Thread(this).start();
+// }
+//
+// @Override
+// public void handleMessage(Message message) {
+// progressDialog.dismiss();
+//
+// listener.groupAggregationComplete(new Recipients(recipientList));
+// }
+// }
+
+ @Override
+ public Loader onCreateLoader(int arg0, Bundle arg1) {
+ return ContactAccessor.getInstance().getCursorLoaderForContactGroups(getActivity());
+ }
+
+ @Override
+ public void onLoadFinished(Loader arg0, Cursor cursor) {
+ ((CursorAdapter)getListAdapter()).changeCursor(cursor);
+ }
+
+ @Override
+ public void onLoaderReset(Loader arg0) {
+ ((CursorAdapter)getListAdapter()).changeCursor(null);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
similarity index 76%
rename from src/org/thoughtcrime/securesms/ContactSelectionListActivity.java
rename to src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
index b92fe23553..0b61655837 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionListActivity.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
@@ -1,6 +1,6 @@
-/**
+/**
* 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
@@ -10,35 +10,22 @@
* 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 java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.thoughtcrime.securesms.contacts.ContactAccessor;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.Recipients;
-
import android.app.AlertDialog;
-import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
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.KeyEvent;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
@@ -47,85 +34,97 @@ 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.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 ContactSelectionActivity tab frame, and ultimately called by
* ComposeMessageActivity for selecting a list of destination contacts.
- *
+ *
* @author Moxie Marlinspike
*
*/
-public class ContactSelectionListActivity extends ListActivity {
-
- private final HashMap selectedContacts = new HashMap();
+public class ContactSelectionListFragment extends SherlockListFragment
+ implements LoaderManager.LoaderCallbacks
+{
+
+ private final HashMap selectedContacts = new HashMap();
- private static final int MENU_OPTION_EXIT = 1;
- private static final int MENU_OPTION_SELECT_ALL = 2;
- private static final int MENU_OPTION_UNSELECT_ALL = 3;
-
@Override
- protected void onCreate(Bundle icicle) {
+ public void onActivityCreated(Bundle icicle) {
super.onCreate(icicle);
-
- setContentView(R.layout.contact_selection_list_activity);
- initializeResources();
- displayContacts();
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- menu.clear();
-
- menu.add(0, MENU_OPTION_EXIT, Menu.NONE, "Finished!").setIcon(android.R.drawable.ic_menu_set_as);
- menu.add(0, MENU_OPTION_SELECT_ALL, Menu.NONE, "Select all").setIcon(android.R.drawable.ic_menu_add);
- menu.add(0, MENU_OPTION_UNSELECT_ALL, Menu.NONE, "Unselect all").setIcon(android.R.drawable.ic_menu_revert);
- return true;
+ initializeResources();
+ initializeCursor();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.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) {
- super.onOptionsItemSelected(item);
-
+
switch (item.getItemId()) {
- case MENU_OPTION_EXIT: saveAndExit(); return true;
- case MENU_OPTION_SELECT_ALL: selectAll(); return true;
- case MENU_OPTION_UNSELECT_ALL: unselectAll(); return true;
+ case R.id.menu_select_all: handleSelectAll(); return true;
+ case R.id.menu_unselect_all: handleUnselectAll(); return true;
}
-
+
+ super.onOptionsItemSelected(item);
return false;
}
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
- saveAndExit();
- return true;
+
+ 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));
+ }
}
- return super.onKeyDown(keyCode, event);
+ return new Recipients(recipientList);
}
-
- private void unselectAll() {
+
+
+ private void handleUnselectAll() {
selectedContacts.clear();
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
}
-
- private void selectAll() {
- Log.w("ContactSelectionListActivity", "Selecting all...");
+
+ private void handleSelectAll() {
selectedContacts.clear();
Cursor cursor = null;
-
+
try {
- cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(this);
-
+ cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(getActivity());
+
while (cursor != null && cursor.moveToNext()) {
- ContactData contactData = ContactAccessor.getInstance().getContactData(this, cursor);
-
+ ContactData contactData = ContactAccessor.getInstance().getContactData(getActivity(), cursor);
+
if (contactData.numbers.isEmpty()) continue;
else if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
else addMultipleNumberContact(contactData, null);
@@ -137,66 +136,47 @@ public class ContactSelectionListActivity extends ListActivity {
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
}
-
- private void saveAndExit() {
- List recipientList = new LinkedList();
-
- for (ContactData contactData : selectedContacts.values()) {
- for (NumberData numberData : contactData.numbers) {
- recipientList.add(new Recipient(contactData.name, numberData.number, null));
- }
- }
-
- Intent resultIntent = getIntent();
- resultIntent.putExtra("recipients", new Recipients(recipientList));
-
- if (getParent() == null) setResult(RESULT_OK, resultIntent);
- else getParent().setResult(RESULT_OK, resultIntent);
-
- finish();
- }
-
+
private void addSingleNumberContact(ContactData contactData) {
selectedContacts.put(contactData.id, contactData);
}
-
+
private void removeContact(ContactData contactData) {
selectedContacts.remove(contactData.id);
}
-
+
private void addMultipleNumberContact(ContactData contactData, CheckedTextView textView) {
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(this);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Select for " + contactData.name);
builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData));
builder.setPositiveButton("Ok", new DiscriminatorFinishedListener(contactData, textView));
builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView));
builder.show();
}
-
- private void displayContacts() {
- Cursor cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(this);
- startManagingCursor(cursor);
- setListAdapter(new ContactSelectionListAdapter(this, cursor));
+
+ private void initializeCursor() {
+ setListAdapter(new ContactSelectionListAdapter(getActivity(), null));
+ this.getLoaderManager().initLoader(0, null, this);
}
-
+
private void initializeResources() {
this.getListView().setFocusable(true);
}
@Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
+ 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);
}
@@ -213,9 +193,9 @@ public class ContactSelectionListActivity extends ListActivity {
public void bindView(View view, Context context, Cursor cursor) {
ContactData contactData = ContactAccessor.getInstance().getContactData(context, cursor);
((ContactItemView)view).set(contactData);
- }
- }
-
+ }
+ }
+
private class ContactItemView extends RelativeLayout {
private ContactData contactData;
private CheckedTextView name;
@@ -236,40 +216,40 @@ public class ContactSelectionListActivity extends ListActivity {
public void selected() {
name.toggle();
-
+
if (name.isChecked()) {
if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
else addMultipleNumberContact(contactData, name);
} else {
removeContact(contactData);
- }
+ }
}
-
+
public void set(ContactData contactData) {
this.contactData = contactData;
-
+
if (selectedContacts.containsKey(contactData.id))
this.name.setChecked(true);
else
this.name.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);
+ this.label.setText(contactData.numbers.get(0).type);
}
}
}
-
+
private class DiscriminatorFinishedListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
private final ContactData contactData;
private final CheckedTextView textView;
-
+
public DiscriminatorFinishedListener(ContactData contactData, CheckedTextView textView) {
this.contactData = contactData;
this.textView = textView;
@@ -277,14 +257,14 @@ public class ContactSelectionListActivity extends ListActivity {
public void onClick(DialogInterface dialog, int which) {
ContactData selected = selectedContacts.get(contactData.id);
-
+
if (selected == null && textView != null) {
if (textView != null) textView.setChecked(false);
} else if (selected.numbers.size() == 0) {
selectedContacts.remove(selected.id);
if (textView != null) textView.setChecked(false);
}
-
+
if (textView == null)
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
}
@@ -293,38 +273,52 @@ public class ContactSelectionListActivity extends ListActivity {
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();
existing.id = contactData.id;
existing.name = contactData.name;
existing.numbers = new LinkedList();
-
+
selectedContacts.put(existing.id, existing);
}
-
+
NumberData selectedData = contactData.numbers.get(which);
-
+
if (!isChecked) existing.numbers.remove(selectedData);
- else existing.numbers.add(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(cursor);
+ }
+
+ @Override
+ public void onLoaderReset(Loader arg0) {
+ ((CursorAdapter)getListAdapter()).changeCursor(null);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionRecentActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
similarity index 75%
rename from src/org/thoughtcrime/securesms/ContactSelectionRecentActivity.java
rename to src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
index 28b008de33..81b1fefa64 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionRecentActivity.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
@@ -1,6 +1,6 @@
-/**
+/**
* 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
@@ -10,35 +10,21 @@
* 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 java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.thoughtcrime.securesms.contacts.ContactAccessor;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.util.RedPhoneCallTypes;
-
-import android.app.ListActivity;
import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.CallLog.Calls;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
@@ -48,102 +34,82 @@ import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.actionbarsherlock.app.SherlockListFragment;
+
+import org.thoughtcrime.securesms.contacts.ContactAccessor;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
+import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.RedPhoneCallTypes;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
/**
* Displays a list of recently used contacts for multi-select. Displayed
* by the ContactSelectionActivity in a tab frame, and ultimately used by
* ComposeMessageActivity for selecting destination message contacts.
- *
+ *
* @author Moxie Marlinspike
*
*/
-public class ContactSelectionRecentActivity extends ListActivity {
-
- private final HashMap selectedContacts = new HashMap();
+public class ContactSelectionRecentFragment extends SherlockListFragment
+ implements LoaderManager.LoaderCallbacks
+{
+
+ private final HashMap selectedContacts = new HashMap();
- private static final int MENU_OPTION_EXIT = 1;
-
@Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- setContentView(R.layout.contact_selection_recent_activity);
+ public void onActivityCreated(Bundle icicle) {
+ super.onActivityCreated(icicle);
+
initializeResources();
- displayContacts();
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- menu.clear();
- menu.add(0, MENU_OPTION_EXIT, Menu.NONE, "Finished!").setIcon(android.R.drawable.ic_menu_set_as);
- return true;
+ initializeCursor();
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- super.onOptionsItemSelected(item);
-
- switch (item.getItemId()) {
- case MENU_OPTION_EXIT: saveAndExit(); return true;
- }
-
- return false;
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.contact_selection_recent_activity, container, false);
}
@Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
- saveAndExit();
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ ((CallItemView)v).selected();
}
-
- private void saveAndExit() {
+
+ private void initializeCursor() {
+ setListAdapter(new ContactSelectionListAdapter(getActivity(), null));
+ this.getLoaderManager().initLoader(0, null, this);
+ }
+
+ 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));
}
}
-
- Intent resultIntent = getIntent();
- resultIntent.putExtra("recipients", new Recipients(recipientList));
-
- if (getParent() == null) setResult(RESULT_OK, resultIntent);
- else getParent().setResult(RESULT_OK, resultIntent);
-
- finish();
+
+ return new Recipients(recipientList);
}
-
+
private void addSingleNumberContact(ContactData contactData) {
selectedContacts.put(contactData.id, contactData);
}
-
+
private void removeContact(ContactData contactData) {
selectedContacts.remove(contactData.id);
}
-
- private void displayContacts() {
- Cursor cursor = getContentResolver().query(Calls.CONTENT_URI, null, null, null, Calls.DEFAULT_SORT_ORDER);
- startManagingCursor(cursor);
- setListAdapter(new ContactSelectionListAdapter(this, cursor));
- }
-
private void initializeResources() {
this.getListView().setFocusable(true);
}
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- ((CallItemView)v).selected();
- }
-
private class ContactSelectionListAdapter extends CursorAdapter {
-
+
public ContactSelectionListAdapter(Context context, Cursor c) {
super(context, c);
}
@@ -164,11 +130,11 @@ public class ContactSelectionRecentActivity extends ListActivity {
String number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER));
int type = cursor.getInt(cursor.getColumnIndexOrThrow(Calls.TYPE));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE));
-
+
((CallItemView)view).set(id, name, label, number, type, date);
- }
- }
-
+ }
+ }
+
private class CallItemView extends RelativeLayout {
private ContactData contactData;
private Context context;
@@ -176,7 +142,7 @@ public class ContactSelectionRecentActivity extends ListActivity {
private TextView date;
private TextView label;
private TextView number;
- private CheckedTextView line1;
+ private CheckedTextView line1;
public CallItemView(Context context) {
super(context);
@@ -194,41 +160,59 @@ public class ContactSelectionRecentActivity extends ListActivity {
public void selected() {
line1.toggle();
-
+
if (line1.isChecked()) {
addSingleNumberContact(contactData);
} else {
removeContact(contactData);
- }
+ }
}
public void set(long id, String name, String label, String number, int type, long date) {
if( name == null ) {
- name = ContactAccessor.getInstance().getNameForNumber(ContactSelectionRecentActivity.this, number);
+ name = ContactAccessor.getInstance().getNameForNumber(getActivity(), number);
}
-
+
this.line1.setText((name == null || name.equals("")) ? number : name);
this.number.setText((name == null || name.equals("")) ? "" : number);
this.label.setText(label);
this.date.setText(DateUtils.getRelativeDateTimeString(context, date, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE));
-
+
if (type == Calls.INCOMING_TYPE || type == RedPhoneCallTypes.INCOMING) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_incoming_call));
else if (type == Calls.OUTGOING_TYPE || type == RedPhoneCallTypes.OUTGOING) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_outgoing_call));
else if (type == Calls.MISSED_TYPE || type == RedPhoneCallTypes.MISSED) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_missed_call));
-
+
this.contactData = new ContactData();
-
+
if (name != null)
this.contactData.name = name;
-
+
this.contactData.id = id;
this.contactData.numbers = new LinkedList();
this.contactData.numbers.add(new NumberData(null, number));
-
+
if (selectedContacts.containsKey(id))
this.line1.setChecked(true);
else
this.line1.setChecked(false);
}
- }
+ }
+
+ @Override
+ public Loader onCreateLoader(int arg0, Bundle arg1) {
+ return new CursorLoader(getActivity(), Calls.CONTENT_URI,
+ null, null, null,
+ Calls.DEFAULT_SORT_ORDER);
+ }
+
+ @Override
+ public void onLoadFinished(Loader arg0, Cursor cursor) {
+ ((CursorAdapter)getListAdapter()).changeCursor(cursor);
+ }
+
+ @Override
+ public void onLoaderReset(Loader arg0) {
+ ((CursorAdapter)getListAdapter()).changeCursor(null);
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/GroupSelectionListActivity.java b/src/org/thoughtcrime/securesms/GroupSelectionListActivity.java
deleted file mode 100644
index 55dd415c9d..0000000000
--- a/src/org/thoughtcrime/securesms/GroupSelectionListActivity.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/**
- * 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 java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.thoughtcrime.securesms.contacts.ContactAccessor;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.GroupData;
-import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.Recipients;
-
-import android.app.ListActivity;
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckedTextView;
-import android.widget.CursorAdapter;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-
-/**
- * An activity for selecting a list of "contact groups." Displayed
- * by ContactSelectionActivity in a tabbed frame, and ultimately called
- * by ComposeMessageActivity for selecting a list of recipients.
- *
- * @author Moxie Marlinspike
- *
- */
-public class GroupSelectionListActivity extends ListActivity {
-
- private final HashMap selectedGroups = new HashMap();
-
- private static final int MENU_OPTION_EXIT = 1;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- setContentView(R.layout.contact_selection_group_activity);
- initializeResources();
- displayGroups();
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- menu.clear();
- menu.add(0, MENU_OPTION_EXIT, Menu.NONE, "Finished!").setIcon(android.R.drawable.ic_menu_set_as);
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- super.onOptionsItemSelected(item);
-
- switch (item.getItemId()) {
- case MENU_OPTION_EXIT: saveAndExit(); return true;
- }
-
- return false;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
- saveAndExit();
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- private void saveAndExit() {
- GroupAggregationHandler aggregator = new GroupAggregationHandler();
- aggregator.aggregateContactsAndExit();
- }
-
- private void addGroup(GroupData groupData) {
- selectedGroups.put(groupData.id, groupData);
- }
-
- private void removeGroup(GroupData groupData) {
- selectedGroups.remove(groupData.id);
- }
-
- private void displayGroups() {
- Cursor cursor = ContactAccessor.getInstance().getCursorForContactGroups(this);
-
- startManagingCursor(cursor);
- setListAdapter(new GroupSelectionListAdapter(this, cursor));
- }
-
- private void initializeResources() {
- this.getListView().setFocusable(true);
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- ((GroupItemView)v).selected();
- }
-
- private class GroupSelectionListAdapter extends CursorAdapter {
-
- public GroupSelectionListAdapter(Context context, Cursor cursor) {
- super(context, cursor);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- GroupItemView view = new GroupItemView(context);
- bindView(view, context, cursor);
- return view;
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- GroupData groupData = ContactAccessor.getInstance().getGroupData(GroupSelectionListActivity.this, cursor);
- ((GroupItemView)view).set(groupData);
- }
- }
-
- private class GroupItemView extends LinearLayout {
- private GroupData groupData;
- private CheckedTextView name;
-
- public GroupItemView(Context context) {
- super(context);
-
- LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- li.inflate(R.layout.contact_selection_group_item, this, true);
-
- this.name = (CheckedTextView)findViewById(R.id.name);
- }
-
- public void selected() {
- name.toggle();
-
- if (name.isChecked()) {
- addGroup(groupData);
- } else {
- removeGroup(groupData);
- }
- }
-
- public void set(GroupData groupData) {
- this.groupData = groupData;
-
- if (selectedGroups.containsKey(groupData.id))
- this.name.setChecked(true);
- else
- this.name.setChecked(false);
-
- this.name.setText(groupData.name);
- }
- }
-
- private class GroupAggregationHandler extends Handler implements Runnable {
- private List recipientList = new LinkedList();
- private ProgressDialog progressDialog;
-
- public GroupAggregationHandler() {}
-
- public void run() {
- recipientList.clear();
-
- for (GroupData groupData : selectedGroups.values()) {
- List contactDataList = ContactAccessor.getInstance().getGroupMembership(GroupSelectionListActivity.this, groupData.id);
-
- Log.w("GroupSelectionListActivity", "Got contacts in group: " + contactDataList.size());
-
- for (ContactData contactData : contactDataList) {
- for (NumberData numberData : contactData.numbers) {
- recipientList.add(new Recipient(contactData.name, numberData.number, null));
- }
- }
- }
-
- this.obtainMessage().sendToTarget();
- }
-
- public void aggregateContactsAndExit() {
- progressDialog = new ProgressDialog(GroupSelectionListActivity.this);
- progressDialog.setTitle("Aggregating Contacts");
- progressDialog.setMessage("Aggregating group contacts...");
- progressDialog.setCancelable(false);
- progressDialog.setIndeterminate(true);
- progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- progressDialog.show();
- Log.w("GroupSelectionListActivity", "Showing group spinner...");
- new Thread(this).start();
- }
-
- @Override
- public void handleMessage(Message message) {
- progressDialog.dismiss();
-
- Intent resultIntent = getIntent();
- resultIntent.putExtra("recipients", new Recipients(recipientList));
-
- if (getParent() == null) setResult(RESULT_OK, resultIntent);
- else getParent().setResult(RESULT_OK, resultIntent);
-
- finish();
- }
- }
-}
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
index 326c32456b..3a80393efa 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java
@@ -1,6 +1,6 @@
-/**
+/**
* 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
@@ -10,25 +10,25 @@
* 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.contacts;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.thoughtcrime.securesms.crypto.IdentityKey;
-
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.v4.content.CursorLoader;
+
+import org.thoughtcrime.securesms.crypto.IdentityKey;
+
+import java.util.LinkedList;
+import java.util.List;
/**
* Android changed their contacts API pretty heavily between
@@ -36,37 +36,17 @@ import android.os.Parcelable;
* API operations, using a singleton pattern that will Class.forName
* the correct one so we don't trigger NoClassDefFound exceptions on
* old platforms.
- *
+ *
* @author Moxie Marlinspike
*/
-public abstract class ContactAccessor {
+public abstract class ContactAccessor {
public static final int UNIQUE_ID = 0;
public static final int DISPLAY_NAME = 1;
- private static ContactAccessor sInstance;
-
- public static synchronized ContactAccessor getInstance() {
- if (sInstance == null) {
- String className;
+ private static final ContactAccessor sInstance = new ContactAccessorNewApi();
- if (Integer.parseInt(Build.VERSION.SDK) <= Build.VERSION_CODES.DONUT)
- className = "ContactAccessorOldApi";
- else
- className = "ContactAccessorNewApi";
-
- try {
- Class extends ContactAccessor> clazz =
- Class.forName("org.thoughtcrime.securesms.contacts." + className )
- .asSubclass(ContactAccessor.class);
-
- sInstance = clazz.newInstance();
-
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
+ public static synchronized ContactAccessor getInstance() {
return sInstance;
}
@@ -78,6 +58,8 @@ public abstract class ContactAccessor {
public abstract List getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver);
public abstract List getGroupMembership(Context context, long groupId);
public abstract Cursor getCursorForContactGroups(Context context);
+ public abstract CursorLoader getCursorLoaderForContactGroups(Context context);
+ public abstract CursorLoader getCursorLoaderForContactsWithNumbers(Context context);
public abstract Cursor getCursorForContactsWithNumbers(Context context);
public abstract GroupData getGroupData(Context context, Cursor cursor);
public abstract ContactData getContactData(Context context, Cursor cursor);
@@ -85,9 +67,9 @@ public abstract class ContactAccessor {
public abstract CharSequence phoneTypeToString(Context mContext, int type, CharSequence label);
public abstract String getNameForNumber(Context context, String number);
public abstract Uri getContactsUri();
-
+
public static class NumberData implements Parcelable {
-
+
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public NumberData createFromParcel(Parcel in) {
return new NumberData(in);
@@ -105,12 +87,12 @@ public abstract class ContactAccessor {
this.type = type;
this.number = number;
}
-
+
public NumberData(Parcel in) {
number = in.readString();
type = in.readString();
}
-
+
public int describeContents() {
return 0;
}
@@ -120,14 +102,14 @@ public abstract class ContactAccessor {
dest.writeString(type);
}
}
-
+
public static class GroupData {
public long id;
public String name;
}
-
+
public static class ContactData implements Parcelable {
-
+
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public ContactData createFromParcel(Parcel in) {
return new ContactData(in);
@@ -143,18 +125,18 @@ public abstract class ContactAccessor {
public List numbers;
public ContactData() {}
-
+
public ContactData(Parcel in) {
id = in.readLong();
name = in.readString();
numbers = new LinkedList();
in.readTypedList(numbers, NumberData.CREATOR);
}
-
+
public int describeContents() {
return 0;
}
-
+
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(name);
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java
index 3c27b898ce..a8a36e3ab4 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessorNewApi.java
@@ -1,6 +1,6 @@
-/**
+/**
* 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
@@ -10,21 +10,12 @@
* 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.contacts;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.thoughtcrime.securesms.crypto.IdentityKey;
-import org.thoughtcrime.securesms.crypto.InvalidKeyException;
-import org.thoughtcrime.securesms.util.Base64;
-
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -39,17 +30,27 @@ import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
+import android.support.v4.content.CursorLoader;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
+import org.thoughtcrime.securesms.crypto.IdentityKey;
+import org.thoughtcrime.securesms.crypto.InvalidKeyException;
+import org.thoughtcrime.securesms.util.Base64;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
/**
* Interface into the Android 2.x+ contacts operations.
- *
+ *
* @author Stuart Anderson
*/
public class ContactAccessorNewApi extends ContactAccessor {
-
+
private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," + Contacts.DISPLAY_NAME + "," + Phone.TYPE;
private static final String[] PROJECTION_PHONE = {
@@ -65,22 +66,22 @@ public class ContactAccessorNewApi extends ContactAccessor {
public List getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
LinkedList numberList = new LinkedList();
Cursor cursor = null;
-
+
try {
- cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(constraint)),
- null, null, null, null);
-
+ cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(constraint)),
+ null, null, null, null);
+
while (cursor != null && cursor.moveToNext())
numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
-
+
} finally {
if (cursor != null)
cursor.close();
}
-
+
return numberList;
}
-
+
@Override
public Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver) {
String phone = "";
@@ -97,22 +98,22 @@ public class ContactAccessorNewApi extends ContactAccessor {
}
}
}
-
+
Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
String selection = String.format("%s=%s OR %s=%s OR %s=%s",
- Phone.TYPE,
- Phone.TYPE_MOBILE,
- Phone.TYPE,
- Phone.TYPE_WORK_MOBILE,
- Phone.TYPE,
- Phone.TYPE_MMS);
+ Phone.TYPE,
+ Phone.TYPE_MOBILE,
+ Phone.TYPE,
+ Phone.TYPE_WORK_MOBILE,
+ Phone.TYPE,
+ Phone.TYPE_MMS);
Cursor phoneCursor = mContentResolver.query(uri,
- PROJECTION_PHONE,
- null,
- null,
- SORT_ORDER);
-
+ PROJECTION_PHONE,
+ null,
+ null,
+ SORT_ORDER);
+
if (phone.length() > 0) {
@@ -139,9 +140,9 @@ public class ContactAccessorNewApi extends ContactAccessor {
} else {
return phoneCursor;
}
-
+
}
-
+
@Override
public CharSequence phoneTypeToString( Context mContext, int type, CharSequence label ) {
return Phone.getTypeLabel(mContext.getResources(), type, label);
@@ -156,50 +157,50 @@ public class ContactAccessorNewApi extends ContactAccessor {
private long getContactIdFromLookupUri(Context context, Uri uri) {
Cursor cursor = null;
-
+
try {
cursor = context.getContentResolver().query(uri, new String[] {ContactsContract.Contacts._ID}, null, null, null);
-
+
if (cursor != null && cursor.moveToFirst())
return cursor.getLong(0);
else
return -1;
-
+
} finally {
if (cursor != null)
cursor.close();
}
}
-
+
private ArrayList getRawContactIds(Context context, long contactId) {
Cursor cursor = null;
ArrayList rawContactIds = new ArrayList();
-
+
try {
- cursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
- RawContacts.CONTACT_ID + " = ?", new String[] {contactId+""},
- null);
-
+ cursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
+ RawContacts.CONTACT_ID + " = ?", new String[] {contactId+""},
+ null);
+
if (cursor == null)
return rawContactIds;
-
+
while (cursor.moveToNext()) {
rawContactIds.add(new Long(cursor.getLong(0)));
- }
+ }
} finally {
if (cursor != null)
cursor.close();
}
-
+
return rawContactIds;
}
-
+
@Override
public void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey) {
long contactId = getContactIdFromLookupUri(context, uri);
Log.w("ContactAccessorNewApi", "Got contact ID: " + contactId + " from uri: " + uri.toString());
ArrayList rawContactIds = getRawContactIds(context, contactId);
-
+
for (long rawContactId : rawContactIds) {
Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId);
ContentValues contentValues = new ContentValues();
@@ -208,9 +209,9 @@ public class ContactAccessorNewApi extends ContactAccessor {
contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey");
contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize()));
-
- context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
- }
+
+ context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
+ }
}
@Override
@@ -218,16 +219,16 @@ public class ContactAccessorNewApi extends ContactAccessor {
long contactId = getContactIdFromLookupUri(context, uri);
String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?";
String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"};
-
+
Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null);
-
+
try {
if (cursor != null && cursor.moveToFirst()) {
String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA));
-
- if (data != null)
+
+ if (data != null)
return new IdentityKey(Base64.decode(data), 0);
-
+
}
} catch (InvalidKeyException e) {
Log.w("ContactAccessorNewApi", e);
@@ -246,10 +247,10 @@ public class ContactAccessorNewApi extends ContactAccessor {
@Override
public String getNameFromContact(Context context, Uri uri) {
Cursor cursor = null;
-
+
try {
cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME}, null, null, null);
-
+
if (cursor != null && cursor.moveToFirst())
return cursor.getString(0);
@@ -257,24 +258,24 @@ public class ContactAccessorNewApi extends ContactAccessor {
if (cursor != null)
cursor.close();
}
-
+
return null;
}
-
+
private String getMobileNumberForId(Context context, long id) {
Cursor cursor = null;
-
+
try {
- cursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ? AND " + Phone.TYPE + " = ?",
- new String[] {id+"", Phone.TYPE_MOBILE+""}, null);
-
+ cursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ? AND " + Phone.TYPE + " = ?",
+ new String[] {id+"", Phone.TYPE_MOBILE+""}, null);
+
if (cursor != null && cursor.moveToFirst())
return cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER));
} finally {
if (cursor != null)
cursor.close();
}
-
+
return null;
}
@@ -282,29 +283,41 @@ public class ContactAccessorNewApi extends ContactAccessor {
public NameAndNumber getNameAndNumberFromContact(Context context, Uri uri) {
Log.w("ContactAccessorNewApi", "Get name and number from: " + uri.toString());
Cursor cursor = null;
-
+
try {
NameAndNumber results = new NameAndNumber();
cursor = context.getContentResolver().query(uri, new String[] {Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
-
+
if (cursor != null && cursor.moveToFirst()) {
results.name = cursor.getString(1);
results.number = getMobileNumberForId(context, cursor.getLong(0));
return results;
}
-
+
} finally {
if (cursor != null)
cursor.close();
}
-
+
return null;
}
@Override
- public Cursor getCursorForContactsWithNumbers(Context context) {
+ public CursorLoader getCursorLoaderForContactsWithNumbers(Context context) {
+ Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
- return context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, selection, null, ContactsContract.Contacts.DISPLAY_NAME + " ASC");
+
+ return new CursorLoader(context, uri, null, selection, null,
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC");
+ }
+
+ @Override
+ public Cursor getCursorForContactsWithNumbers(Context context) {
+ Uri uri = ContactsContract.Contacts.CONTENT_URI;
+ String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
+
+ return context.getContentResolver().query(uri, null, selection, null,
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
private ContactData getContactData(Context context, String displayName, long id) {
@@ -312,52 +325,58 @@ public class ContactAccessorNewApi extends ContactAccessor {
contactData.id = id;
contactData.name = displayName;
contactData.numbers = new LinkedList();
-
+
Cursor numberCursor = null;
-
+
try {
- numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ?",
- new String[] {contactData.id + ""}, null);
-
+ numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ?",
+ new String[] {contactData.id + ""}, null);
+
while (numberCursor != null && numberCursor.moveToNext())
- contactData.numbers.add(new NumberData(Phone.getTypeLabel(context.getResources(),
- numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)),
- numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL))).toString(),
- numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER))));
+ contactData.numbers.add(new NumberData(Phone.getTypeLabel(context.getResources(),
+ numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)),
+ numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL))).toString(),
+ numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER))));
} finally {
if (numberCursor != null)
numberCursor.close();
}
-
+
return contactData;
-
+
}
-
+
@Override
public ContactData getContactData(Context context, Cursor cursor) {
- return getContactData(context,
- cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
- cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
+ return getContactData(context,
+ cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
+ cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
}
-
+
@Override
public Cursor getCursorForContactGroups(Context context) {
return context.getContentResolver().query(ContactsContract.Groups.CONTENT_URI, null, null, null, ContactsContract.Groups.TITLE + " ASC");
}
+ @Override
+ public CursorLoader getCursorLoaderForContactGroups(Context context) {
+ return new CursorLoader(context, ContactsContract.Groups.CONTENT_URI,
+ null, null, null, ContactsContract.Groups.TITLE + " ASC");
+ }
+
@Override
public List getGroupMembership(Context context, long groupId) {
- LinkedList contacts = new LinkedList();
+ LinkedList contacts = new LinkedList();
Cursor groupMembership = null;
-
+
try {
- String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
+ String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
String[] args = new String[] {groupId+"",
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
-
+
groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
-
+
while (groupMembership != null && groupMembership.moveToNext()) {
String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
@@ -368,16 +387,16 @@ public class ContactAccessorNewApi extends ContactAccessor {
if (groupMembership != null)
groupMembership.close();
}
-
+
return contacts;
}
-
+
@Override
public GroupData getGroupData(Context context, Cursor cursor) {
GroupData groupData = new GroupData();
groupData.id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
groupData.name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
-
+
return groupData;
}
@@ -393,7 +412,7 @@ public class ContactAccessorNewApi extends ContactAccessor {
if (cursor != null)
cursor.close();
}
-
+
return null;
}
@@ -401,5 +420,5 @@ public class ContactAccessorNewApi extends ContactAccessor {
public Uri getContactsUri() {
return ContactsContract.Contacts.CONTENT_URI;
}
-
+
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessorOldApi.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessorOldApi.java
deleted file mode 100644
index 9c2d5113e8..0000000000
--- a/src/org/thoughtcrime/securesms/contacts/ContactAccessorOldApi.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/**
- * 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.contacts;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.thoughtcrime.securesms.crypto.IdentityKey;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.MergeCursor;
-import android.net.Uri;
-import android.provider.Contacts;
-import android.provider.Contacts.GroupMembership;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-import android.widget.Toast;
-
-
-/**
- * A contact interface into the 1.x API for older clients.
- *
- * @author Stuart Anderson
- */
-
-public class ContactAccessorOldApi extends ContactAccessor {
-
- @SuppressWarnings("deprecation")
- private static final String SORT_ORDER = Phones.NAME + "," + Phones.TYPE;
- @SuppressWarnings("deprecation")
- private static final String[] PROJECTION_PHONE = {
- Phones._ID, // 0
- Phones.PERSON_ID, // 1
- Phones.TYPE, // 2
- Phones.NUMBER, // 3
- Phones.LABEL, // 4
- Phones.DISPLAY_NAME, // 5
- };
-
- @SuppressWarnings("deprecation")
- @Override
- public Cursor getCursorForRecipientFilter(CharSequence constraint,
- ContentResolver mContentResolver) {
- String phone = "";
- String wherePhone = null;
-
- String cons = null;
- if (constraint != null) {
- cons = constraint.toString();
-
- if (RecipientsAdapter.usefulAsDigits(cons)) {
- phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
- if (phone.equals(cons)) {
- phone = "";
- } else {
- phone = phone.trim();
- }
- }
- }
-
- String filter = DatabaseUtils.sqlEscapeString(cons + '%');
- String filterLastName = DatabaseUtils.sqlEscapeString("% " + cons + '%');
-
- StringBuilder s = new StringBuilder();
- s.append("((name LIKE ");
- s.append(filter);
- s.append(") OR (name LIKE ");
- s.append(filterLastName);
- s.append(") OR (REPLACE(REPLACE(REPLACE(REPLACE(number, ' ', ''), '(', ''), ')', ''), '-', '') LIKE ");
- s.append(filter);
- s.append("))");
-
- wherePhone = s.toString();
-
- Cursor phoneCursor = mContentResolver.query(Phones.CONTENT_URI,
- PROJECTION_PHONE,
- wherePhone,
- null,
- SORT_ORDER);
-
- //dumpCursor(phoneCursor);
-
-
- if (phone.length() > 0) {
- ArrayList result = new ArrayList();
- result.add(Integer.valueOf(-1)); // ID
- result.add(Long.valueOf(-1)); // CONTACT_ID
- result.add(Integer.valueOf(Phones.TYPE_CUSTOM)); // TYPE
- result.add(phone); // NUMBER
-
- /*
- * The "\u00A0" keeps Phone.getDisplayLabel() from deciding
- * to display the default label ("Home") next to the transformation
- * of the letters into numbers.
- */
- result.add("\u00A0"); // LABEL
- result.add(cons); // NAME
-
- ArrayList wrap = new ArrayList();
- wrap.add(result);
-
- ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap);
-
- return new MergeCursor(new Cursor[] { translated, phoneCursor });
- } else {
- return phoneCursor;
- }
-
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public CharSequence phoneTypeToString(Context mContext, int type,
- CharSequence label) {
- return Phones.getDisplayLabel(mContext, type, label);
- }
-
- public static void dumpCursor( Cursor c ) {
- c.moveToFirst();
- Log.d( "DC", "Begin:" );
- for( int i=0; i < c.getCount(); i++ ) {
- String rowStr = "";
- for( int j=0; j < c.getColumnCount(); j++ ) {
- rowStr = rowStr + c.getColumnName(j) + "=" + c.getString(j) +" ";
- }
- Log.d( "DC", rowStr + "\n" );
- c.moveToNext();
- }
- }
-
- @Override
- public Intent getIntentForContactSelection() {
- return new Intent(Intent.ACTION_PICK, People.CONTENT_URI);
- }
-
- @Override
- public void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey) {
- Toast.makeText(context, "Sorry, reading and writing identity keys to the contacts database is not supported on Android 1.X", Toast.LENGTH_LONG).show();
- }
-
- @Override
- public IdentityKey importIdentityKey(Context context, Uri uri) {
- Toast.makeText(context, "Sorry, reading and writing identity keys to the contacts database is not supported on Android 1.X", Toast.LENGTH_LONG).show();
- return null;
- }
-
- @Override
- public String getNameFromContact(Context context, Uri uri) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
- LinkedList numberList = new LinkedList();
- Cursor cursor = null;
-
- try {
- cursor = contentResolver.query(Uri.withAppendedPath(Contacts.People.CONTENT_FILTER_URI, Uri.encode(constraint)),
- null, null, null, null);
-
- while (cursor != null && cursor.moveToNext()) {
- String number = cursor.getString(cursor.getColumnIndexOrThrow(Contacts.Phones.NUMBER));
- if (number != null)
- numberList.add(number);
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return numberList;
- }
-
- @Override
- public NameAndNumber getNameAndNumberFromContact(Context context, Uri uri) {
- Cursor cursor = null;
- NameAndNumber result = new NameAndNumber();
-
- try {
- cursor = context.getContentResolver().query(uri, null, null, null, null);
-
- if (cursor != null && cursor.moveToFirst()) {
- result.name = cursor.getString(cursor.getColumnIndexOrThrow(People.NAME));
- result.number = cursor.getString(cursor.getColumnIndexOrThrow(People.NUMBER));
-
- return result;
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- @Override
- public Cursor getCursorForContactsWithNumbers(Context context) {
- return context.getContentResolver().query(People.CONTENT_URI,new String[]{People._ID,People.DISPLAY_NAME},
- People.NUMBER + " NOT NULL", null, "UPPER( " + People.DISPLAY_NAME + " ) ASC");
- }
-
- @Override
- public ContactData getContactData(Context context, Cursor cursor) {
- ContactData contactData = new ContactData();
- contactData.id = cursor.getLong(cursor.getColumnIndexOrThrow(People._ID));
- contactData.name = cursor.getString(cursor.getColumnIndexOrThrow(People.DISPLAY_NAME));
- contactData.numbers = getNumberDataForPersonId(context, contactData.id);
-
- return contactData;
- }
-
- @Override
- public Cursor getCursorForContactGroups(Context context) {
- return context.getContentResolver().query(Contacts.Groups.CONTENT_URI, null, null, null, Contacts.Groups.NAME + " ASC");
- }
-
- private LinkedList getNumberDataForPersonId(Context context, long personId) {
- LinkedList numbers = new LinkedList();
- Cursor numberCursor = context.getContentResolver().query(Phones.CONTENT_URI, null,
- Phones.PERSON_ID + " = ?",
- new String[] {personId+""}, null);
- try {
- while (numberCursor != null && numberCursor.moveToNext()) {
- numbers.add(new NumberData(Phones.getDisplayLabel(context, numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phones.TYPE)), "").toString(),
- numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phones.NUMBER))));
- }
- } finally {
- if (numberCursor != null)
- numberCursor.close();
- }
-
- return numbers;
- }
-
- private ContactData getContactDataFromGroupMembership(Context context, Cursor cursor) {
- ContactData contactData = new ContactData();
- contactData.id = cursor.getLong(cursor.getColumnIndexOrThrow(GroupMembership.PERSON_ID));
-
- Cursor personCursor = context.getContentResolver().query(Uri.withAppendedPath(People.CONTENT_URI, contactData.id+""), null, null, null, null);
-
- try {
- if (personCursor == null || !personCursor.moveToFirst())
- throw new AssertionError("Non-existent user in group?");
-
- contactData.name = personCursor.getString(personCursor.getColumnIndexOrThrow(People.DISPLAY_NAME));
- contactData.numbers = getNumberDataForPersonId(context, contactData.id);
-
- return contactData;
-
- } finally {
- if (personCursor != null)
- personCursor.close();
- }
- }
-
- @Override
- public List getGroupMembership(Context context, long groupId) {
- LinkedList contacts = new LinkedList();
- Cursor groupMembershipCursor = context.getContentResolver().query(Contacts.GroupMembership.CONTENT_URI, null,
- GroupMembership.GROUP_ID + " = ?",
- new String[] {groupId+""}, null);
-
- try {
- while (groupMembershipCursor != null && groupMembershipCursor.moveToNext()) {
- contacts.add(getContactDataFromGroupMembership(context, groupMembershipCursor));
- }
- } finally {
- if (groupMembershipCursor != null)
- groupMembershipCursor.close();
- }
-
- return contacts;
- }
-
- @Override
- public GroupData getGroupData(Context context, Cursor cursor) {
- GroupData groupData = new GroupData();
- groupData.id = cursor.getLong(cursor.getColumnIndexOrThrow(Contacts.Groups._ID));
- groupData.name = cursor.getString(cursor.getColumnIndexOrThrow(Contacts.Groups.NAME));
-
- return groupData;
- }
-
- @Override
- public String getNameForNumber(Context context, String number) {
- Cursor cursor = context.getContentResolver().query(Contacts.Phones.CONTENT_URI, null,
- Phones.NUMBER + " = ?",
- new String[] {number}, null);
-
- try {
- if (cursor != null && cursor.moveToFirst())
- return cursor.getString(cursor.getColumnIndexOrThrow(Contacts.Phones.DISPLAY_NAME));
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- return null;
- }
-
- @Override
- public Uri getContactsUri() {
- return Contacts.People.CONTENT_URI;
- }
-
-}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipients.java b/src/org/thoughtcrime/securesms/recipients/Recipients.java
index b1d60ddd41..540313b9aa 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipients.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipients.java
@@ -1,6 +1,6 @@
-/**
+/**
* 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
@@ -10,20 +10,20 @@
* 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.recipients;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
+import android.os.Parcel;
+import android.os.Parcelable;
import org.thoughtcrime.securesms.util.NumberUtil;
-import android.os.Parcel;
-import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
public class Recipients implements Parcelable {
@@ -37,73 +37,77 @@ public class Recipients implements Parcelable {
}
};
-
+
private List recipients;
-
+
public Recipients(List recipients) {
this.recipients = recipients;
}
-
+
public Recipients(Parcel in) {
this.recipients = new ArrayList();
in.readTypedList(recipients, Recipient.CREATOR);
}
-
+
+ public void append(Recipients recipients) {
+ this.recipients.addAll(recipients.getRecipientsList());
+ }
+
public Recipients truncateToSingleRecipient() {
assert(!this.recipients.isEmpty());
this.recipients = this.recipients.subList(0, 1);
return this;
}
-
+
public boolean isEmailRecipient() {
for (Recipient recipient : recipients) {
if (NumberUtil.isValidEmail(recipient.getNumber()))
- return true;
+ return true;
}
-
+
return false;
}
-
+
public boolean isEmpty() {
return this.recipients.isEmpty();
}
-
+
public boolean isSingleRecipient() {
return this.recipients.size() == 1;
}
-
+
public Recipient getPrimaryRecipient() {
if (!isEmpty())
return this.recipients.get(0);
else
return null;
}
-
+
public List getRecipientsList() {
return this.recipients;
}
-
+
public String[] toNumberStringArray() {
String[] recipientsArray = new String[recipients.size()];
Iterator iterator = recipients.iterator();
int i = 0;
-
+
while (iterator.hasNext())
recipientsArray[i++] = iterator.next().getNumber();
-
+
return recipientsArray;
}
public String toShortString() {
String fromString = "";
-
+
for (int i=0;i