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:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"/> 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=".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=".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> <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=".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=".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=".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.KeyCachingService"></service>
<service android:enabled="true" android:name=".service.SendReceiveService"></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"?> <?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"); <LinearLayout android:id="@+id/fragment_container"
you may not use this file except in compliance with the License. android:layout_height="match_parent"
You may obtain a copy of the License at android:layout_width="match_parent"/>
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>

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 * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import org.thoughtcrime.securesms.service.KeyCachingService;
import android.app.TabActivity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.Window; import android.support.v4.app.Fragment;
import android.widget.TabHost; 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 * Activity container for selecting a list of contacts. Provides a tab frame for
* contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity * contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity
* when selecting a list of contacts to address a message to. * when selecting a list of contacts to address a message to.
* *
* @author Moxie Marlinspike * @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 @Override
protected void onCreate(Bundle icicle) { protected void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_NO_TITLE); ActionBar actionBar = this.getSupportActionBar();
setContentView(R.layout.contact_selection_activity); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayHomeAsUpEnabled(true);
tabHost = getTabHost(); setContentView(R.layout.contact_selection_activity);
tabHost.setOnTabChangedListener(this);
setupContactsTab(); setupContactsTab();
setupGroupsTab(); setupGroupsTab();
setupRecentTab(); setupRecentTab();
} }
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
registerPassphraseActivityStarted(); registerPassphraseActivityStarted();
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
@ -65,48 +77,102 @@ public class ContactSelectionActivity extends TabActivity implements TabHost.OnT
} }
@Override @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; 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() { private void registerPassphraseActivityStarted() {
Intent intent = new Intent(this, KeyCachingService.class); Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT); intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
startService(intent); startService(intent);
} }
private void registerPassphraseActivityStopped() { private void registerPassphraseActivityStopped() {
Intent intent = new Intent(this, KeyCachingService.class); Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT); intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
startService(intent); 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 * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms; 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.AlertDialog;
import android.app.ListActivity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckedTextView; import android.widget.CheckedTextView;
@ -47,85 +34,97 @@ import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; 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 * Activity for selecting a list of contacts. Displayed inside
* a ContactSelectionActivity tab frame, and ultimately called by * a ContactSelectionActivity tab frame, and ultimately called by
* ComposeMessageActivity for selecting a list of destination contacts. * ComposeMessageActivity for selecting a list of destination contacts.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
* *
*/ */
public class ContactSelectionListActivity extends ListActivity { public class ContactSelectionListFragment extends SherlockListFragment
implements LoaderManager.LoaderCallbacks<Cursor>
private final HashMap<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>(); {
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 @Override
protected void onCreate(Bundle icicle) { public void onActivityCreated(Bundle icicle) {
super.onCreate(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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) { switch (item.getItemId()) {
case MENU_OPTION_EXIT: saveAndExit(); return true; case R.id.menu_select_all: handleSelectAll(); return true;
case MENU_OPTION_SELECT_ALL: selectAll(); return true; case R.id.menu_unselect_all: handleUnselectAll(); return true;
case MENU_OPTION_UNSELECT_ALL: unselectAll(); return true;
} }
super.onOptionsItemSelected(item);
return false; return false;
} }
@Override public Recipients getSelectedContacts() {
public boolean onKeyDown(int keyCode, KeyEvent event) { List<Recipient> recipientList = new LinkedList<Recipient>();
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
saveAndExit(); for (ContactData contactData : selectedContacts.values()) {
return true; 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(); selectedContacts.clear();
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged(); ((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
} }
private void selectAll() { private void handleSelectAll() {
Log.w("ContactSelectionListActivity", "Selecting all...");
selectedContacts.clear(); selectedContacts.clear();
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(this); cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(getActivity());
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
ContactData contactData = ContactAccessor.getInstance().getContactData(this, cursor); ContactData contactData = ContactAccessor.getInstance().getContactData(getActivity(), cursor);
if (contactData.numbers.isEmpty()) continue; if (contactData.numbers.isEmpty()) continue;
else if (contactData.numbers.size() == 1) addSingleNumberContact(contactData); else if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
else addMultipleNumberContact(contactData, null); else addMultipleNumberContact(contactData, null);
@ -137,66 +136,47 @@ public class ContactSelectionListActivity extends ListActivity {
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged(); ((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) { private void addSingleNumberContact(ContactData contactData) {
selectedContacts.put(contactData.id, contactData); selectedContacts.put(contactData.id, contactData);
} }
private void removeContact(ContactData contactData) { private void removeContact(ContactData contactData) {
selectedContacts.remove(contactData.id); selectedContacts.remove(contactData.id);
} }
private void addMultipleNumberContact(ContactData contactData, CheckedTextView textView) { private void addMultipleNumberContact(ContactData contactData, CheckedTextView textView) {
String[] options = new String[contactData.numbers.size()]; String[] options = new String[contactData.numbers.size()];
int i = 0; int i = 0;
for (NumberData option : contactData.numbers) { for (NumberData option : contactData.numbers) {
options[i++] = option.type + " " + option.number; 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.setTitle("Select for " + contactData.name);
builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData)); builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData));
builder.setPositiveButton("Ok", new DiscriminatorFinishedListener(contactData, textView)); builder.setPositiveButton("Ok", new DiscriminatorFinishedListener(contactData, textView));
builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView)); builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView));
builder.show(); builder.show();
} }
private void displayContacts() { private void initializeCursor() {
Cursor cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(this); setListAdapter(new ContactSelectionListAdapter(getActivity(), null));
startManagingCursor(cursor); this.getLoaderManager().initLoader(0, null, this);
setListAdapter(new ContactSelectionListAdapter(this, cursor));
} }
private void initializeResources() { private void initializeResources() {
this.getListView().setFocusable(true); this.getListView().setFocusable(true);
} }
@Override @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(); ((ContactItemView)v).selected();
} }
private class ContactSelectionListAdapter extends CursorAdapter { private class ContactSelectionListAdapter extends CursorAdapter {
public ContactSelectionListAdapter(Context context, Cursor c) { public ContactSelectionListAdapter(Context context, Cursor c) {
super(context, c); super(context, c);
} }
@ -213,9 +193,9 @@ public class ContactSelectionListActivity extends ListActivity {
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
ContactData contactData = ContactAccessor.getInstance().getContactData(context, cursor); ContactData contactData = ContactAccessor.getInstance().getContactData(context, cursor);
((ContactItemView)view).set(contactData); ((ContactItemView)view).set(contactData);
} }
} }
private class ContactItemView extends RelativeLayout { private class ContactItemView extends RelativeLayout {
private ContactData contactData; private ContactData contactData;
private CheckedTextView name; private CheckedTextView name;
@ -236,40 +216,40 @@ public class ContactSelectionListActivity extends ListActivity {
public void selected() { public void selected() {
name.toggle(); name.toggle();
if (name.isChecked()) { if (name.isChecked()) {
if (contactData.numbers.size() == 1) addSingleNumberContact(contactData); if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
else addMultipleNumberContact(contactData, name); else addMultipleNumberContact(contactData, name);
} else { } else {
removeContact(contactData); removeContact(contactData);
} }
} }
public void set(ContactData contactData) { public void set(ContactData contactData) {
this.contactData = contactData; this.contactData = contactData;
if (selectedContacts.containsKey(contactData.id)) if (selectedContacts.containsKey(contactData.id))
this.name.setChecked(true); this.name.setChecked(true);
else else
this.name.setChecked(false); this.name.setChecked(false);
this.name.setText(contactData.name); this.name.setText(contactData.name);
if (contactData.numbers.isEmpty()) { if (contactData.numbers.isEmpty()) {
this.name.setEnabled(false); this.name.setEnabled(false);
this.number.setText(""); this.number.setText("");
this.label.setText(""); this.label.setText("");
} else { } else {
this.number.setText(contactData.numbers.get(0).number); 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 class DiscriminatorFinishedListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
private final ContactData contactData; private final ContactData contactData;
private final CheckedTextView textView; private final CheckedTextView textView;
public DiscriminatorFinishedListener(ContactData contactData, CheckedTextView textView) { public DiscriminatorFinishedListener(ContactData contactData, CheckedTextView textView) {
this.contactData = contactData; this.contactData = contactData;
this.textView = textView; this.textView = textView;
@ -277,14 +257,14 @@ public class ContactSelectionListActivity extends ListActivity {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
ContactData selected = selectedContacts.get(contactData.id); ContactData selected = selectedContacts.get(contactData.id);
if (selected == null && textView != null) { if (selected == null && textView != null) {
if (textView != null) textView.setChecked(false); if (textView != null) textView.setChecked(false);
} else if (selected.numbers.size() == 0) { } else if (selected.numbers.size() == 0) {
selectedContacts.remove(selected.id); selectedContacts.remove(selected.id);
if (textView != null) textView.setChecked(false); if (textView != null) textView.setChecked(false);
} }
if (textView == null) if (textView == null)
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged(); ((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
} }
@ -293,38 +273,52 @@ public class ContactSelectionListActivity extends ListActivity {
onClick(dialog, 0); onClick(dialog, 0);
} }
} }
private class DiscriminatorClickedListener implements DialogInterface.OnMultiChoiceClickListener { private class DiscriminatorClickedListener implements DialogInterface.OnMultiChoiceClickListener {
private final ContactData contactData; private final ContactData contactData;
public DiscriminatorClickedListener(ContactData contactData) { public DiscriminatorClickedListener(ContactData contactData) {
this.contactData = contactData; this.contactData = contactData;
} }
public void onClick(DialogInterface dialog, int which, boolean isChecked) { public void onClick(DialogInterface dialog, int which, boolean isChecked) {
Log.w("ContactSelectionListActivity", "Got checked: " + isChecked); Log.w("ContactSelectionListActivity", "Got checked: " + isChecked);
ContactData existing = selectedContacts.get(contactData.id); ContactData existing = selectedContacts.get(contactData.id);
if (existing == null) { if (existing == null) {
Log.w("ContactSelectionListActivity", "No existing contact data, creating..."); Log.w("ContactSelectionListActivity", "No existing contact data, creating...");
if (!isChecked) if (!isChecked)
throw new AssertionError("We shouldn't be unchecking data that doesn't exist."); throw new AssertionError("We shouldn't be unchecking data that doesn't exist.");
existing = new ContactData(); existing = new ContactData();
existing.id = contactData.id; existing.id = contactData.id;
existing.name = contactData.name; existing.name = contactData.name;
existing.numbers = new LinkedList<NumberData>(); existing.numbers = new LinkedList<NumberData>();
selectedContacts.put(existing.id, existing); selectedContacts.put(existing.id, existing);
} }
NumberData selectedData = contactData.numbers.get(which); NumberData selectedData = contactData.numbers.get(which);
if (!isChecked) existing.numbers.remove(selectedData); 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 * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms; 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.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.provider.CallLog.Calls; 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.text.format.DateUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckedTextView; import android.widget.CheckedTextView;
@ -48,102 +34,82 @@ import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; 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 * Displays a list of recently used contacts for multi-select. Displayed
* by the ContactSelectionActivity in a tab frame, and ultimately used by * by the ContactSelectionActivity in a tab frame, and ultimately used by
* ComposeMessageActivity for selecting destination message contacts. * ComposeMessageActivity for selecting destination message contacts.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
* *
*/ */
public class ContactSelectionRecentActivity extends ListActivity { public class ContactSelectionRecentFragment extends SherlockListFragment
implements LoaderManager.LoaderCallbacks<Cursor>
private final HashMap<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>(); {
private final HashMap<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>();
private static final int MENU_OPTION_EXIT = 1;
@Override @Override
protected void onCreate(Bundle icicle) { public void onActivityCreated(Bundle icicle) {
super.onCreate(icicle); super.onActivityCreated(icicle);
setContentView(R.layout.contact_selection_recent_activity);
initializeResources(); initializeResources();
displayContacts(); initializeCursor();
}
@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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onOptionsItemSelected(item); return inflater.inflate(R.layout.contact_selection_recent_activity, container, false);
switch (item.getItemId()) {
case MENU_OPTION_EXIT: saveAndExit(); return true;
}
return false;
} }
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public void onListItemClick(ListView l, View v, int position, long id) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { ((CallItemView)v).selected();
saveAndExit();
return true;
}
return super.onKeyDown(keyCode, event);
} }
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>(); List<Recipient> recipientList = new LinkedList<Recipient>();
for (ContactData contactData : selectedContacts.values()) { for (ContactData contactData : selectedContacts.values()) {
for (NumberData numberData : contactData.numbers) { for (NumberData numberData : contactData.numbers) {
recipientList.add(new Recipient(contactData.name, numberData.number, null)); recipientList.add(new Recipient(contactData.name, numberData.number, null));
} }
} }
Intent resultIntent = getIntent(); return new Recipients(recipientList);
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) { private void addSingleNumberContact(ContactData contactData) {
selectedContacts.put(contactData.id, contactData); selectedContacts.put(contactData.id, contactData);
} }
private void removeContact(ContactData contactData) { private void removeContact(ContactData contactData) {
selectedContacts.remove(contactData.id); 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() { private void initializeResources() {
this.getListView().setFocusable(true); this.getListView().setFocusable(true);
} }
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
((CallItemView)v).selected();
}
private class ContactSelectionListAdapter extends CursorAdapter { private class ContactSelectionListAdapter extends CursorAdapter {
public ContactSelectionListAdapter(Context context, Cursor c) { public ContactSelectionListAdapter(Context context, Cursor c) {
super(context, c); super(context, c);
} }
@ -164,11 +130,11 @@ public class ContactSelectionRecentActivity extends ListActivity {
String number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER)); String number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER));
int type = cursor.getInt(cursor.getColumnIndexOrThrow(Calls.TYPE)); int type = cursor.getInt(cursor.getColumnIndexOrThrow(Calls.TYPE));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE));
((CallItemView)view).set(id, name, label, number, type, date); ((CallItemView)view).set(id, name, label, number, type, date);
} }
} }
private class CallItemView extends RelativeLayout { private class CallItemView extends RelativeLayout {
private ContactData contactData; private ContactData contactData;
private Context context; private Context context;
@ -176,7 +142,7 @@ public class ContactSelectionRecentActivity extends ListActivity {
private TextView date; private TextView date;
private TextView label; private TextView label;
private TextView number; private TextView number;
private CheckedTextView line1; private CheckedTextView line1;
public CallItemView(Context context) { public CallItemView(Context context) {
super(context); super(context);
@ -194,41 +160,59 @@ public class ContactSelectionRecentActivity extends ListActivity {
public void selected() { public void selected() {
line1.toggle(); line1.toggle();
if (line1.isChecked()) { if (line1.isChecked()) {
addSingleNumberContact(contactData); addSingleNumberContact(contactData);
} else { } else {
removeContact(contactData); removeContact(contactData);
} }
} }
public void set(long id, String name, String label, String number, int type, long date) { public void set(long id, String name, String label, String number, int type, long date) {
if( name == null ) { 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.line1.setText((name == null || name.equals("")) ? number : name);
this.number.setText((name == null || name.equals("")) ? "" : number); this.number.setText((name == null || name.equals("")) ? "" : number);
this.label.setText(label); this.label.setText(label);
this.date.setText(DateUtils.getRelativeDateTimeString(context, date, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE)); 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)); 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.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)); 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(); this.contactData = new ContactData();
if (name != null) if (name != null)
this.contactData.name = name; this.contactData.name = name;
this.contactData.id = id; this.contactData.id = id;
this.contactData.numbers = new LinkedList<ContactAccessor.NumberData>(); this.contactData.numbers = new LinkedList<ContactAccessor.NumberData>();
this.contactData.numbers.add(new NumberData(null, number)); this.contactData.numbers.add(new NumberData(null, number));
if (selectedContacts.containsKey(id)) if (selectedContacts.containsKey(id))
this.line1.setChecked(true); this.line1.setChecked(true);
else else
this.line1.setChecked(false); 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 * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms.contacts; 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.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; 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 * 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 * API operations, using a singleton pattern that will Class.forName
* the correct one so we don't trigger NoClassDefFound exceptions on * the correct one so we don't trigger NoClassDefFound exceptions on
* old platforms. * old platforms.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
*/ */
public abstract class ContactAccessor { public abstract class ContactAccessor {
public static final int UNIQUE_ID = 0; public static final int UNIQUE_ID = 0;
public static final int DISPLAY_NAME = 1; public static final int DISPLAY_NAME = 1;
private static ContactAccessor sInstance; private static final ContactAccessor sInstance = new ContactAccessorNewApi();
public static synchronized ContactAccessor getInstance() {
if (sInstance == null) {
String className;
if (Integer.parseInt(Build.VERSION.SDK) <= Build.VERSION_CODES.DONUT) public static synchronized ContactAccessor getInstance() {
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);
}
}
return sInstance; return sInstance;
} }
@ -78,6 +58,8 @@ public abstract class ContactAccessor {
public abstract List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver); public abstract List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver);
public abstract List<ContactData> getGroupMembership(Context context, long groupId); public abstract List<ContactData> getGroupMembership(Context context, long groupId);
public abstract Cursor getCursorForContactGroups(Context context); 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 Cursor getCursorForContactsWithNumbers(Context context);
public abstract GroupData getGroupData(Context context, Cursor cursor); public abstract GroupData getGroupData(Context context, Cursor cursor);
public abstract ContactData getContactData(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 CharSequence phoneTypeToString(Context mContext, int type, CharSequence label);
public abstract String getNameForNumber(Context context, String number); public abstract String getNameForNumber(Context context, String number);
public abstract Uri getContactsUri(); public abstract Uri getContactsUri();
public static class NumberData implements Parcelable { public static class NumberData implements Parcelable {
public static final Parcelable.Creator<NumberData> CREATOR = new Parcelable.Creator<NumberData>() { public static final Parcelable.Creator<NumberData> CREATOR = new Parcelable.Creator<NumberData>() {
public NumberData createFromParcel(Parcel in) { public NumberData createFromParcel(Parcel in) {
return new NumberData(in); return new NumberData(in);
@ -105,12 +87,12 @@ public abstract class ContactAccessor {
this.type = type; this.type = type;
this.number = number; this.number = number;
} }
public NumberData(Parcel in) { public NumberData(Parcel in) {
number = in.readString(); number = in.readString();
type = in.readString(); type = in.readString();
} }
public int describeContents() { public int describeContents() {
return 0; return 0;
} }
@ -120,14 +102,14 @@ public abstract class ContactAccessor {
dest.writeString(type); dest.writeString(type);
} }
} }
public static class GroupData { public static class GroupData {
public long id; public long id;
public String name; public String name;
} }
public static class ContactData implements Parcelable { public static class ContactData implements Parcelable {
public static final Parcelable.Creator<ContactData> CREATOR = new Parcelable.Creator<ContactData>() { public static final Parcelable.Creator<ContactData> CREATOR = new Parcelable.Creator<ContactData>() {
public ContactData createFromParcel(Parcel in) { public ContactData createFromParcel(Parcel in) {
return new ContactData(in); return new ContactData(in);
@ -143,18 +125,18 @@ public abstract class ContactAccessor {
public List<NumberData> numbers; public List<NumberData> numbers;
public ContactData() {} public ContactData() {}
public ContactData(Parcel in) { public ContactData(Parcel in) {
id = in.readLong(); id = in.readLong();
name = in.readString(); name = in.readString();
numbers = new LinkedList<NumberData>(); numbers = new LinkedList<NumberData>();
in.readTypedList(numbers, NumberData.CREATOR); in.readTypedList(numbers, NumberData.CREATOR);
} }
public int describeContents() { public int describeContents() {
return 0; return 0;
} }
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id); dest.writeLong(id);
dest.writeString(name); dest.writeString(name);

View File

@ -1,6 +1,6 @@
/** /**
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms.contacts; 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.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
@ -39,17 +30,27 @@ import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import android.support.v4.content.CursorLoader;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.util.Log; 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. * Interface into the Android 2.x+ contacts operations.
* *
* @author Stuart Anderson * @author Stuart Anderson
*/ */
public class ContactAccessorNewApi extends ContactAccessor { public class ContactAccessorNewApi extends ContactAccessor {
private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," + Contacts.DISPLAY_NAME + "," + Phone.TYPE; private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," + Contacts.DISPLAY_NAME + "," + Phone.TYPE;
private static final String[] PROJECTION_PHONE = { private static final String[] PROJECTION_PHONE = {
@ -65,22 +66,22 @@ public class ContactAccessorNewApi extends ContactAccessor {
public List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) { public List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
LinkedList<String> numberList = new LinkedList<String>(); LinkedList<String> numberList = new LinkedList<String>();
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(constraint)), cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(constraint)),
null, null, null, null); null, null, null, null);
while (cursor != null && cursor.moveToNext()) while (cursor != null && cursor.moveToNext())
numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER))); numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
return numberList; return numberList;
} }
@Override @Override
public Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver) { public Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver) {
String phone = ""; String phone = "";
@ -97,22 +98,22 @@ public class ContactAccessorNewApi extends ContactAccessor {
} }
} }
} }
Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons)); Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
String selection = String.format("%s=%s OR %s=%s OR %s=%s", String selection = String.format("%s=%s OR %s=%s OR %s=%s",
Phone.TYPE, Phone.TYPE,
Phone.TYPE_MOBILE, Phone.TYPE_MOBILE,
Phone.TYPE, Phone.TYPE,
Phone.TYPE_WORK_MOBILE, Phone.TYPE_WORK_MOBILE,
Phone.TYPE, Phone.TYPE,
Phone.TYPE_MMS); Phone.TYPE_MMS);
Cursor phoneCursor = mContentResolver.query(uri, Cursor phoneCursor = mContentResolver.query(uri,
PROJECTION_PHONE, PROJECTION_PHONE,
null, null,
null, null,
SORT_ORDER); SORT_ORDER);
if (phone.length() > 0) { if (phone.length() > 0) {
@ -139,9 +140,9 @@ public class ContactAccessorNewApi extends ContactAccessor {
} else { } else {
return phoneCursor; return phoneCursor;
} }
} }
@Override @Override
public CharSequence phoneTypeToString( Context mContext, int type, CharSequence label ) { public CharSequence phoneTypeToString( Context mContext, int type, CharSequence label ) {
return Phone.getTypeLabel(mContext.getResources(), type, label); return Phone.getTypeLabel(mContext.getResources(), type, label);
@ -156,50 +157,50 @@ public class ContactAccessorNewApi extends ContactAccessor {
private long getContactIdFromLookupUri(Context context, Uri uri) { private long getContactIdFromLookupUri(Context context, Uri uri) {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = context.getContentResolver().query(uri, new String[] {ContactsContract.Contacts._ID}, null, null, null); cursor = context.getContentResolver().query(uri, new String[] {ContactsContract.Contacts._ID}, null, null, null);
if (cursor != null && cursor.moveToFirst()) if (cursor != null && cursor.moveToFirst())
return cursor.getLong(0); return cursor.getLong(0);
else else
return -1; return -1;
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
} }
private ArrayList<Long> getRawContactIds(Context context, long contactId) { private ArrayList<Long> getRawContactIds(Context context, long contactId) {
Cursor cursor = null; Cursor cursor = null;
ArrayList<Long> rawContactIds = new ArrayList<Long>(); ArrayList<Long> rawContactIds = new ArrayList<Long>();
try { try {
cursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, cursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ?", new String[] {contactId+""}, RawContacts.CONTACT_ID + " = ?", new String[] {contactId+""},
null); null);
if (cursor == null) if (cursor == null)
return rawContactIds; return rawContactIds;
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
rawContactIds.add(new Long(cursor.getLong(0))); rawContactIds.add(new Long(cursor.getLong(0)));
} }
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
return rawContactIds; return rawContactIds;
} }
@Override @Override
public void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey) { public void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey) {
long contactId = getContactIdFromLookupUri(context, uri); long contactId = getContactIdFromLookupUri(context, uri);
Log.w("ContactAccessorNewApi", "Got contact ID: " + contactId + " from uri: " + uri.toString()); Log.w("ContactAccessorNewApi", "Got contact ID: " + contactId + " from uri: " + uri.toString());
ArrayList<Long> rawContactIds = getRawContactIds(context, contactId); ArrayList<Long> rawContactIds = getRawContactIds(context, contactId);
for (long rawContactId : rawContactIds) { for (long rawContactId : rawContactIds) {
Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId); Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId);
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
@ -208,9 +209,9 @@ public class ContactAccessorNewApi extends ContactAccessor {
contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM); contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey"); contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey");
contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize())); contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize()));
context.getContentResolver().insert(Data.CONTENT_URI, contentValues); context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
} }
} }
@Override @Override
@ -218,16 +219,16 @@ public class ContactAccessorNewApi extends ContactAccessor {
long contactId = getContactIdFromLookupUri(context, uri); long contactId = getContactIdFromLookupUri(context, uri);
String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?"; String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?";
String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"}; String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"};
Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null); Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null);
try { try {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA)); String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA));
if (data != null) if (data != null)
return new IdentityKey(Base64.decode(data), 0); return new IdentityKey(Base64.decode(data), 0);
} }
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.w("ContactAccessorNewApi", e); Log.w("ContactAccessorNewApi", e);
@ -246,10 +247,10 @@ public class ContactAccessorNewApi extends ContactAccessor {
@Override @Override
public String getNameFromContact(Context context, Uri uri) { public String getNameFromContact(Context context, Uri uri) {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME}, null, null, null); cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME}, null, null, null);
if (cursor != null && cursor.moveToFirst()) if (cursor != null && cursor.moveToFirst())
return cursor.getString(0); return cursor.getString(0);
@ -257,24 +258,24 @@ public class ContactAccessorNewApi extends ContactAccessor {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
return null; return null;
} }
private String getMobileNumberForId(Context context, long id) { private String getMobileNumberForId(Context context, long id) {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ? AND " + Phone.TYPE + " = ?", cursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ? AND " + Phone.TYPE + " = ?",
new String[] {id+"", Phone.TYPE_MOBILE+""}, null); new String[] {id+"", Phone.TYPE_MOBILE+""}, null);
if (cursor != null && cursor.moveToFirst()) if (cursor != null && cursor.moveToFirst())
return cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)); return cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER));
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
return null; return null;
} }
@ -282,29 +283,41 @@ public class ContactAccessorNewApi extends ContactAccessor {
public NameAndNumber getNameAndNumberFromContact(Context context, Uri uri) { public NameAndNumber getNameAndNumberFromContact(Context context, Uri uri) {
Log.w("ContactAccessorNewApi", "Get name and number from: " + uri.toString()); Log.w("ContactAccessorNewApi", "Get name and number from: " + uri.toString());
Cursor cursor = null; Cursor cursor = null;
try { try {
NameAndNumber results = new NameAndNumber(); NameAndNumber results = new NameAndNumber();
cursor = context.getContentResolver().query(uri, new String[] {Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null); cursor = context.getContentResolver().query(uri, new String[] {Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
results.name = cursor.getString(1); results.name = cursor.getString(1);
results.number = getMobileNumberForId(context, cursor.getLong(0)); results.number = getMobileNumberForId(context, cursor.getLong(0));
return results; return results;
} }
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
return null; return null;
} }
@Override @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"; 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) { private ContactData getContactData(Context context, String displayName, long id) {
@ -312,52 +325,58 @@ public class ContactAccessorNewApi extends ContactAccessor {
contactData.id = id; contactData.id = id;
contactData.name = displayName; contactData.name = displayName;
contactData.numbers = new LinkedList<NumberData>(); contactData.numbers = new LinkedList<NumberData>();
Cursor numberCursor = null; Cursor numberCursor = null;
try { try {
numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ?", numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ?",
new String[] {contactData.id + ""}, null); new String[] {contactData.id + ""}, null);
while (numberCursor != null && numberCursor.moveToNext()) while (numberCursor != null && numberCursor.moveToNext())
contactData.numbers.add(new NumberData(Phone.getTypeLabel(context.getResources(), contactData.numbers.add(new NumberData(Phone.getTypeLabel(context.getResources(),
numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)), numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)),
numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL))).toString(), numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL))).toString(),
numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER)))); numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER))));
} finally { } finally {
if (numberCursor != null) if (numberCursor != null)
numberCursor.close(); numberCursor.close();
} }
return contactData; return contactData;
} }
@Override @Override
public ContactData getContactData(Context context, Cursor cursor) { public ContactData getContactData(Context context, Cursor cursor) {
return getContactData(context, return getContactData(context,
cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)), cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID))); cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
} }
@Override @Override
public Cursor getCursorForContactGroups(Context context) { public Cursor getCursorForContactGroups(Context context) {
return context.getContentResolver().query(ContactsContract.Groups.CONTENT_URI, null, null, null, ContactsContract.Groups.TITLE + " ASC"); 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 @Override
public List<ContactData> getGroupMembership(Context context, long groupId) { public List<ContactData> getGroupMembership(Context context, long groupId) {
LinkedList<ContactData> contacts = new LinkedList<ContactData>(); LinkedList<ContactData> contacts = new LinkedList<ContactData>();
Cursor groupMembership = null; Cursor groupMembership = null;
try { try {
String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " + String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?"; ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
String[] args = new String[] {groupId+"", String[] args = new String[] {groupId+"",
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE}; ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null); groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
while (groupMembership != null && groupMembership.moveToNext()) { while (groupMembership != null && groupMembership.moveToNext()) {
String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME)); String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID)); long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
@ -368,16 +387,16 @@ public class ContactAccessorNewApi extends ContactAccessor {
if (groupMembership != null) if (groupMembership != null)
groupMembership.close(); groupMembership.close();
} }
return contacts; return contacts;
} }
@Override @Override
public GroupData getGroupData(Context context, Cursor cursor) { public GroupData getGroupData(Context context, Cursor cursor) {
GroupData groupData = new GroupData(); GroupData groupData = new GroupData();
groupData.id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID)); groupData.id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
groupData.name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE)); groupData.name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
return groupData; return groupData;
} }
@ -393,7 +412,7 @@ public class ContactAccessorNewApi extends ContactAccessor {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
} }
return null; return null;
} }
@ -401,5 +420,5 @@ public class ContactAccessorNewApi extends ContactAccessor {
public Uri getContactsUri() { public Uri getContactsUri() {
return ContactsContract.Contacts.CONTENT_URI; 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 * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms.recipients; package org.thoughtcrime.securesms.recipients;
import java.util.ArrayList; import android.os.Parcel;
import java.util.Iterator; import android.os.Parcelable;
import java.util.List;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import android.os.Parcel; import java.util.ArrayList;
import android.os.Parcelable; import java.util.Iterator;
import java.util.List;
public class Recipients implements Parcelable { public class Recipients implements Parcelable {
@ -37,73 +37,77 @@ public class Recipients implements Parcelable {
} }
}; };
private List<Recipient> recipients; private List<Recipient> recipients;
public Recipients(List<Recipient> recipients) { public Recipients(List<Recipient> recipients) {
this.recipients = recipients; this.recipients = recipients;
} }
public Recipients(Parcel in) { public Recipients(Parcel in) {
this.recipients = new ArrayList<Recipient>(); this.recipients = new ArrayList<Recipient>();
in.readTypedList(recipients, Recipient.CREATOR); in.readTypedList(recipients, Recipient.CREATOR);
} }
public void append(Recipients recipients) {
this.recipients.addAll(recipients.getRecipientsList());
}
public Recipients truncateToSingleRecipient() { public Recipients truncateToSingleRecipient() {
assert(!this.recipients.isEmpty()); assert(!this.recipients.isEmpty());
this.recipients = this.recipients.subList(0, 1); this.recipients = this.recipients.subList(0, 1);
return this; return this;
} }
public boolean isEmailRecipient() { public boolean isEmailRecipient() {
for (Recipient recipient : recipients) { for (Recipient recipient : recipients) {
if (NumberUtil.isValidEmail(recipient.getNumber())) if (NumberUtil.isValidEmail(recipient.getNumber()))
return true; return true;
} }
return false; return false;
} }
public boolean isEmpty() { public boolean isEmpty() {
return this.recipients.isEmpty(); return this.recipients.isEmpty();
} }
public boolean isSingleRecipient() { public boolean isSingleRecipient() {
return this.recipients.size() == 1; return this.recipients.size() == 1;
} }
public Recipient getPrimaryRecipient() { public Recipient getPrimaryRecipient() {
if (!isEmpty()) if (!isEmpty())
return this.recipients.get(0); return this.recipients.get(0);
else else
return null; return null;
} }
public List<Recipient> getRecipientsList() { public List<Recipient> getRecipientsList() {
return this.recipients; return this.recipients;
} }
public String[] toNumberStringArray() { public String[] toNumberStringArray() {
String[] recipientsArray = new String[recipients.size()]; String[] recipientsArray = new String[recipients.size()];
Iterator<Recipient> iterator = recipients.iterator(); Iterator<Recipient> iterator = recipients.iterator();
int i = 0; int i = 0;
while (iterator.hasNext()) while (iterator.hasNext())
recipientsArray[i++] = iterator.next().getNumber(); recipientsArray[i++] = iterator.next().getNumber();
return recipientsArray; return recipientsArray;
} }
public String toShortString() { public String toShortString() {
String fromString = ""; String fromString = "";
for (int i=0;i<recipients.size();i++) { for (int i=0;i<recipients.size();i++) {
fromString += recipients.get(i).toShortString(); fromString += recipients.get(i).toShortString();
if (i != recipients.size() -1 ) if (i != recipients.size() -1 )
fromString += ", "; fromString += ", ";
} }
return fromString; return fromString;
} }