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"
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android">
android:layout_height="fill_parent">
<TabWidget android:id="@android:id/tabs" <LinearLayout android:id="@+id/fragment_container"
android:layout_width="fill_parent" android:layout_height="match_parent"
android:layout_height="68dip" android:layout_width="match_parent"/>
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> </LinearLayout>
</TabHost>

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

@ -16,13 +16,22 @@
*/ */
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
@ -32,20 +41,23 @@ import android.widget.TabHost;
* @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();
@ -65,10 +77,90 @@ 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);
@ -81,32 +173,6 @@ public class ContactSelectionActivity extends TabActivity implements TabHost.OnT
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

@ -17,28 +17,15 @@
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,6 +34,21 @@ 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
@ -56,75 +58,72 @@ import android.widget.TextView;
* *
*/ */
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(); initializeResources();
displayContacts(); initializeCursor();
} }
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onPrepareOptionsMenu(menu); return inflater.inflate(R.layout.contact_selection_list_activity, container, false);
menu.clear(); }
menu.add(0, MENU_OPTION_EXIT, Menu.NONE, "Finished!").setIcon(android.R.drawable.ic_menu_set_as); @Override
menu.add(0, MENU_OPTION_SELECT_ALL, Menu.NONE, "Select all").setIcon(android.R.drawable.ic_menu_add); public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add(0, MENU_OPTION_UNSELECT_ALL, Menu.NONE, "Unselect all").setIcon(android.R.drawable.ic_menu_revert); inflater.inflate(R.menu.contact_selection_list, menu);
super.onCreateOptionsMenu(menu, inflater);
return true;
} }
@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);
@ -138,24 +137,6 @@ 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);
} }
@ -172,7 +153,7 @@ public class ContactSelectionListActivity extends ListActivity {
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));
@ -180,10 +161,9 @@ public class ContactSelectionListActivity extends ListActivity {
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() {
@ -191,7 +171,7 @@ public class ContactSelectionListActivity extends ListActivity {
} }
@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();
} }
@ -327,4 +307,18 @@ public class ContactSelectionListActivity extends ListActivity {
} }
} }
@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

@ -16,29 +16,15 @@
*/ */
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,6 +34,19 @@ 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
@ -56,51 +55,36 @@ import android.widget.TextView;
* @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 @Override
public boolean onPrepareOptionsMenu(Menu menu) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onPrepareOptionsMenu(menu); return inflater.inflate(R.layout.contact_selection_recent_activity, container, false);
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 void onListItemClick(ListView l, View v, int position, long id) {
super.onOptionsItemSelected(item); ((CallItemView)v).selected();
switch (item.getItemId()) {
case MENU_OPTION_EXIT: saveAndExit(); return true;
} }
return false; private void initializeCursor() {
setListAdapter(new ContactSelectionListAdapter(getActivity(), null));
this.getLoaderManager().initLoader(0, null, this);
} }
@Override public Recipients getSelectedContacts() {
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() {
List<Recipient> recipientList = new LinkedList<Recipient>(); List<Recipient> recipientList = new LinkedList<Recipient>();
for (ContactData contactData : selectedContacts.values()) { for (ContactData contactData : selectedContacts.values()) {
@ -109,13 +93,7 @@ public class ContactSelectionRecentActivity extends ListActivity {
} }
} }
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) {
@ -126,22 +104,10 @@ public class ContactSelectionRecentActivity extends ListActivity {
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) {
@ -204,7 +170,7 @@ public class ContactSelectionRecentActivity extends ListActivity {
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);
@ -231,4 +197,22 @@ public class ContactSelectionRecentActivity extends ListActivity {
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

@ -16,19 +16,19 @@
*/ */
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
@ -44,29 +44,9 @@ 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() { public static synchronized ContactAccessor getInstance() {
if (sInstance == null) {
String className;
if (Integer.parseInt(Build.VERSION.SDK) <= Build.VERSION_CODES.DONUT)
className = "ContactAccessorOldApi";
else
className = "ContactAccessorNewApi";
try {
Class<? extends ContactAccessor> clazz =
Class.forName("org.thoughtcrime.securesms.contacts." + className )
.asSubclass(ContactAccessor.class);
sInstance = clazz.newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
}
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);

View File

@ -16,15 +16,6 @@
*/ */
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,9 +30,19 @@ 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.
* *
@ -302,9 +303,21 @@ public class ContactAccessorNewApi extends ContactAccessor {
} }
@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) {
@ -345,6 +358,12 @@ public class ContactAccessorNewApi extends ContactAccessor {
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>();

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

@ -16,14 +16,14 @@
*/ */
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 {
@ -49,6 +49,10 @@ public class Recipients implements Parcelable {
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);