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 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