Fix the look of tabbed multi-contact selector.

1) Updated to ActionBar style.
2) Split out into fragments.
3) Switch to cursor loaders.
This commit is contained in:
Moxie Marlinspike 2012-07-20 22:23:25 -07:00
parent c7e891eda4
commit 863e1c6508
31 changed files with 743 additions and 1040 deletions

View File

@ -39,6 +39,11 @@
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"/>
<activity android:name=".ContactSelectionActivity"
android:label="Select Contacts"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"/>
<activity android:name=".AutoInitiateActivity" android:theme="@android:style/Theme.Dialog" android:label="TextSecure Messaging Detected" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".ApplicationPreferencesActivity" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".PassphraseCreateActivity" android:theme="@android:style/Theme.Dialog" android:label="Create Passphrase" android:launchMode="singleInstance" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
@ -51,11 +56,6 @@
<activity android:name=".ViewIdentityActivity" android:theme="@android:style/Theme.Dialog" android:label="Public Identity Key" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".SaveIdentityActivity" android:theme="@android:style/Theme.Dialog" android:label="Save Identity" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".ReviewIdentitiesActivity" android:theme="@android:style/Theme.Dialog" android:label="Manage Identity Keys (Long click)" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".ContactSelectionActivity" android:label="Contact Selection" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".ContactSelectionListActivity" android:label="Contact Selection" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".GroupSelectionListActivity" android:label="Group Selection" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<activity android:name=".ContactSelectionRecentActivity" android:label="Recent Calls Selection" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
<service android:enabled="true" android:name=".service.KeyCachingService"></service>
<service android:enabled="true" android:name=".service.SendReceiveService"></service>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,42 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project
<LinearLayout android:layout_gravity="center"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="68dip"
android:paddingLeft="1dip"
android:paddingRight="1dip"
android:paddingTop="4dip"
/>
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
/>
</LinearLayout>
</TabHost>
<LinearLayout android:id="@+id/fragment_container"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Finished"
android:id="@+id/menu_selection_finished"
android:icon="@drawable/ic_menu_done_holo_dark"
android:showAsAction="ifRoom"
/>
</menu>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Select All"
android:id="@+id/menu_select_all" />
<item android:title="Unselect All"
android:id="@+id/menu_unselect_all" />
</menu>

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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) {
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Cursor>
{
private final HashMap<Long, GroupData> selectedGroups = new HashMap<Long, GroupData>();
@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<Recipient> recipientList = new LinkedList<Recipient>();
for (GroupData groupData : selectedGroups.values()) {
List<ContactData> 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<Recipient> recipientList = new LinkedList<Recipient>();
// 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<ContactData> 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<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return ContactAccessor.getInstance().getCursorLoaderForContactGroups(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
((CursorAdapter)getListAdapter()).changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
((CursorAdapter)getListAdapter()).changeCursor(null);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>();
public class ContactSelectionListFragment extends SherlockListFragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
private final HashMap<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>();
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<Recipient> recipientList = new LinkedList<Recipient>();
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<Recipient> recipientList = new LinkedList<Recipient>();
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<NumberData>();
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<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return ContactAccessor.getInstance().getCursorLoaderForContactsWithNumbers(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
((CursorAdapter)getListAdapter()).changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
((CursorAdapter)getListAdapter()).changeCursor(null);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>();
public class ContactSelectionRecentFragment extends SherlockListFragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
private final HashMap<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>();
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<Recipient> recipientList = new LinkedList<Recipient>();
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<ContactAccessor.NumberData>();
this.contactData.numbers.add(new NumberData(null, number));
if (selectedContacts.containsKey(id))
this.line1.setChecked(true);
else
this.line1.setChecked(false);
}
}
}
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(getActivity(), Calls.CONTENT_URI,
null, null, null,
Calls.DEFAULT_SORT_ORDER);
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
((CursorAdapter)getListAdapter()).changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
((CursorAdapter)getListAdapter()).changeCursor(null);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Long, GroupData> selectedGroups = new HashMap<Long, GroupData>();
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<Recipient> recipientList = new LinkedList<Recipient>();
private ProgressDialog progressDialog;
public GroupAggregationHandler() {}
public void run() {
recipientList.clear();
for (GroupData groupData : selectedGroups.values()) {
List<ContactData> 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();
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver);
public abstract List<ContactData> 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<NumberData> CREATOR = new Parcelable.Creator<NumberData>() {
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<ContactData> CREATOR = new Parcelable.Creator<ContactData>() {
public ContactData createFromParcel(Parcel in) {
return new ContactData(in);
@ -143,18 +125,18 @@ public abstract class ContactAccessor {
public List<NumberData> numbers;
public ContactData() {}
public ContactData(Parcel in) {
id = in.readLong();
name = in.readString();
numbers = new LinkedList<NumberData>();
in.readTypedList(numbers, NumberData.CREATOR);
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(name);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
LinkedList<String> numberList = new LinkedList<String>();
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<Long> getRawContactIds(Context context, long contactId) {
Cursor cursor = null;
ArrayList<Long> rawContactIds = new ArrayList<Long>();
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<Long> 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<NumberData>();
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<ContactData> getGroupMembership(Context context, long groupId) {
LinkedList<ContactData> contacts = new LinkedList<ContactData>();
LinkedList<ContactData> contacts = new LinkedList<ContactData>();
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ArrayList> wrap = new ArrayList<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<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
LinkedList<String> numberList = new LinkedList<String>();
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<NumberData> getNumberDataForPersonId(Context context, long personId) {
LinkedList<NumberData> numbers = new LinkedList<NumberData>();
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<ContactData> getGroupMembership(Context context, long groupId) {
LinkedList<ContactData> contacts = new LinkedList<ContactData>();
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Recipient> recipients;
public Recipients(List<Recipient> recipients) {
this.recipients = recipients;
}
public Recipients(Parcel in) {
this.recipients = new ArrayList<Recipient>();
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<Recipient> getRecipientsList() {
return this.recipients;
}
public String[] toNumberStringArray() {
String[] recipientsArray = new String[recipients.size()];
Iterator<Recipient> 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<recipients.size();i++) {
fromString += recipients.get(i).toShortString();
if (i != recipients.size() -1 )
fromString += ", ";
fromString += ", ";
}
return fromString;
}