This commit is contained in:
Jake McGinty 2014-01-18 18:17:08 -08:00
parent 49daa45dca
commit 9fd2c4753e
44 changed files with 1042 additions and 6 deletions

View File

@ -101,6 +101,10 @@
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".GroupCreateActivity"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseMigrationActivity"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:launchMode="singleTask"
@ -130,6 +134,12 @@
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".AutoInitiateActivity"
android:theme="@style/TextSecure.Light.Dialog"
android:label="@string/AndroidManifest__textsecure_detected"

View File

@ -41,11 +41,16 @@ public class Directory {
SUPPORTS_SMS + " INTEGER, " +
TIMESTAMP + " INTEGER);";
private static Directory instance;
private static final Object instanceLock = new Object();
private static volatile Directory instance;
public synchronized static Directory getInstance(Context context) {
public static Directory getInstance(Context context) {
if (instance == null) {
instance = new Directory(context);
synchronized (instanceLock) {
if (instance == null) {
instance = new Directory(context);
}
}
}
return instance;

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<EditText
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:hint="Group name"
android:layout_marginBottom="20dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Add people"
android:padding="10dp" />
<ImageButton
android:id="@+id/add_people_button"
android:background="#00ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="15dp"
android:src="@drawable/ic_action_add_person"
android:layout_alignParentRight="true" />
</RelativeLayout>
<ListView
android:id="@+id/selected_contacts_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/contact_selection_list_fragment"
android:name="org.thoughtcrime.securesms.PushContactSelectionListFragment">
</fragment>
</LinearLayout>

View File

@ -0,0 +1,20 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fastScrollEnabled="true" />
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|center_vertical"
android:layout_marginTop="15dp"
android:text="@string/contact_selection_group_activity__finding_contacts"
android:textSize="20sp" />
</LinearLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingLeft="14dip"
android:paddingRight="25dip">
<TextView android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="8dip"
android:layout_marginTop="-8dip"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold"
android:visibility = "gone"
/>
<TextView android:id="@+id/number"
android:visibility = "visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dip"
android:layout_marginTop="-8dip"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall"
android:fontFamily="sans-serif-light"
/>
<TextView android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/number"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="1dip"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:singleLine="true"
android:ellipsize="marquee"
android:gravity="center_vertical|left"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<CheckBox
android:id="@+id/check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginTop="13dp"
android:focusable="false"
android:clickable="false" />
</RelativeLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:padding="10dp">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/name"
android:textAppearance="?android:attr/textAppearanceSmall"
android:fontFamily="sans-serif-light" />
<ImageButton
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="#00ffffff"
android:src="@drawable/ic_menu_remove_holo_light" />
</RelativeLayout>

10
res/menu/group_create.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="CREATE"
android:id="@+id/menu_create_group"
android:icon="@drawable/ic_menu_accept_holo_light"
android:showAsAction="always|withText"/>
</menu>

View File

@ -5,6 +5,10 @@
android:icon="?attr/menu_new_conversation_icon"
android:showAsAction="always" />
<item android:title="New Group"
android:id="@+id/menu_new_group"
android:icon="?attr/menu_new_group_icon" />
<item android:title="@string/text_secure_normal__menu_clear_passphrase"
android:id="@+id/menu_clear_passphrase"
android:icon="@android:drawable/ic_menu_close_clear_cancel" />

View File

@ -33,6 +33,7 @@
<attr name="navigation_drawer_shadow" format="reference"/>
<attr name="menu_new_conversation_icon" format="reference" />
<attr name="menu_new_group_icon" format="reference" />
<attr name="menu_search_icon" format="reference" />
<attr name="menu_call_icon" format="reference" />
<attr name="menu_unlock_icon" format="reference" />

View File

@ -370,7 +370,8 @@
<!-- contact_selection_group_activity -->
<!-- contact_selection_list_activity -->
<string name="contact_selection_group_activity__no_contacts">No contacts.</string>
<string name="contact_selection_group_activity__finding_contacts">Finding contacts...</string>
<!-- ContactSelectionListFragment-->
<string name="ContactSelectionlistFragment_select_for">Select for</string>

View File

@ -27,6 +27,7 @@
<item name="conversation_keyboard_toggle">@drawable/ic_ime_dark</item>
<item name="menu_new_conversation_icon">@drawable/ic_action_new_holo_light</item>
<item name="menu_new_group_icon">@drawable/ic_action_add_group_holo_light</item>
<item name="menu_search_icon">@drawable/ic_menu_search_holo_light</item>
<item name="menu_call_icon">@drawable/ic_menu_call_holo_light</item>
<item name="menu_unlock_icon">@drawable/ic_menu_unlock_holo_light</item>
@ -75,6 +76,7 @@
<item name="conversation_keyboard_toggle">@drawable/ic_ime_light</item>
<item name="menu_new_conversation_icon">@drawable/ic_action_new_holo_dark</item>
<item name="menu_new_group_icon">@drawable/ic_action_add_group_holo_dark</item>
<item name="menu_search_icon">@drawable/ic_menu_search_holo_dark</item>
<item name="menu_call_icon">@drawable/ic_menu_call_holo_dark</item>
<item name="menu_unlock_icon">@drawable/ic_menu_unlock_holo_dark</item>

View File

@ -19,7 +19,6 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;

View File

@ -659,7 +659,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
}
private void initializeReceivers() {
securityUpdateReceiver = new BroadcastReceiver() {
@Override

View File

@ -140,6 +140,7 @@ public class ConversationFragment extends SherlockListFragment
long dateReceived = message.getDateReceived();
long dateSent = message.getDateSent();
SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d, yyyy 'at' hh:mm:ss a zzz");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_message_details);

View File

@ -139,6 +139,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
switch (item.getItemId()) {
case R.id.menu_new_message: createConversation(-1, null, defaultType); return true;
case R.id.menu_new_group: createGroup(); return true;
case R.id.menu_settings: handleDisplaySettings(); return true;
case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
@ -153,6 +154,11 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
createConversation(threadId, recipients, distributionType);
}
private void createGroup() {
Intent intent = new Intent(this, GroupCreateActivity.class);
startActivity(intent);
}
private void createConversation(long threadId, Recipients recipients, int distributionType) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients);

View File

@ -0,0 +1,151 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.ActionBarUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private static final int PICK_CONTACT = 1;
private static final int SELECT_PHOTO = 100;
private ListView lv;
private Set<Recipient> selectedContacts;
@Override
public void onCreate(Bundle state) {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
super.onCreate(state);
setContentView(R.layout.group_create_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ActionBarUtil.initializeDefaultActionBar(this, getSupportActionBar(), "New Group");
selectedContacts = new HashSet<Recipient>();
initializeResources();
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
private void initializeResources() {
lv = (ListView) findViewById(R.id.selected_contacts_list);
lv.setAdapter(new SelectedRecipientsAdapter(this, android.R.id.text1, new ArrayList<Recipient>()));
(findViewById(R.id.add_people_button)).setOnClickListener(new AddRecipientButtonListener());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = this.getSupportMenuInflater();
menu.clear();
inflater.inflate(R.menu.group_create, menu);
super.onPrepareOptionsMenu(menu);
return true;
}
private List<String> selectedContactsAsIdArray() {
final List<String> ids = new ArrayList<String>();
for (Recipient recipient : selectedContacts) {
ids.add(String.valueOf(recipient.getCanonicalAddress(this)));
}
return ids;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home:
case R.id.menu_create_group:
finish(); // TODO not this
return true;
}
return false;
}
@Override
public void onActivityResult(int reqCode, int resultCode, Intent data) {
Log.w("ComposeMessageActivity", "onActivityResult called: " + resultCode + " , " + data);
super.onActivityResult(reqCode, resultCode, data);
if (data == null || resultCode != Activity.RESULT_OK)
return;
switch (reqCode) {
case PICK_CONTACT:
Recipients recipients = data.getParcelableExtra("recipients");
for (Recipient recipient : recipients.getRecipientsList()) {
if (!selectedContacts.contains(recipient)) {
Log.w("poop", "contains that shit.");
selectedContacts.add(recipient);
}
}
SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter)lv.getAdapter();
adapter.clear();
Iterator<Recipient> selectedContactsIter = selectedContacts.iterator();
while (selectedContactsIter.hasNext()) {
adapter.add(selectedContactsIter.next());
}
break;
case SELECT_PHOTO:
if(resultCode == RESULT_OK){
Uri selectedImage = data.getData();
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(
selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
Bitmap selectedBitmap = BitmapFactory.decodeFile(filePath);
break;
}
}
}
private class AddRecipientButtonListener implements View.OnClickListener {
@Override
public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
startActivityForResult(intent, PICK_CONTACT);
}
}
}

View File

@ -0,0 +1,94 @@
/**
* 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.Intent;
import android.os.Bundle;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.ActionBarUtil;
import org.thoughtcrime.securesms.util.DynamicTheme;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
/**
* Activity container for selecting a list of contacts. Provides a tab frame for
* contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity
* when selecting a list of contacts to address a message to.
*
* @author Moxie Marlinspike
*
*/
public class PushContactSelectionActivity extends PassphraseRequiredSherlockFragmentActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
private Recipients recipients;
@Override
protected void onCreate(Bundle icicle) {
dynamicTheme.onCreate(this);
super.onCreate(icicle);
final ActionBar actionBar = this.getSupportActionBar();
ActionBarUtil.initializeDefaultActionBar(this, actionBar);
actionBar.setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.push_contact_selection_activity);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = this.getSupportMenuInflater();
inflater.inflate(R.menu.contact_selection, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_selection_finished:
case android.R.id.home:
handleSelectionFinished(); return true;
}
return false;
}
private void handleSelectionFinished() {
PushContactSelectionListFragment contactsFragment = (PushContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
recipients = contactsFragment.getSelectedContacts();
Intent resultIntent = getIntent();
resultIntent.putExtra("recipients", this.recipients);
setResult(RESULT_OK, resultIntent);
finish();
}
}

View File

@ -0,0 +1,336 @@
/**
* 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.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
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.CheckBox;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
import org.thoughtcrime.securesms.contacts.PushFilterCursorWrapper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* Activity for selecting a list of contacts. Displayed inside
* a PushContactSelectionActivity tab frame, and ultimately called by
* ComposeMessageActivity for selecting a list of destination contacts.
*
* @author Moxie Marlinspike
*
*/
public class PushContactSelectionListFragment extends SherlockListFragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
private final HashMap<Long, ContactData> selectedContacts = new HashMap<Long, ContactData>();
private static LayoutInflater li;
@Override
public void onActivityCreated(Bundle icicle) {
super.onCreate(icicle);
li = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
initializeResources();
initializeCursor();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.push_contact_selection_list_activity, container, false);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.contact_selection_list, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_select_all: handleSelectAll(); return true;
case R.id.menu_unselect_all: handleUnselectAll(); return true;
}
super.onOptionsItemSelected(item);
return false;
}
public Recipients getSelectedContacts() {
List<Recipient> recipientList = new LinkedList<Recipient>();
for (ContactData contactData : selectedContacts.values()) {
for (NumberData numberData : contactData.numbers) {
recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
}
}
return new Recipients(recipientList);
}
private void handleUnselectAll() {
selectedContacts.clear();
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
}
private void handleSelectAll() {
selectedContacts.clear();
Cursor cursor = null;
try {
cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(getActivity());
while (cursor != null && cursor.moveToNext()) {
ContactData contactData = ContactAccessor.getInstance().getContactData(getActivity(), cursor);
if (contactData.numbers.isEmpty()) continue;
else if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
else addMultipleNumberContact(contactData, null, null);
}
} finally {
if (cursor != null)
cursor.close();
}
((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged();
}
private void addSingleNumberContact(ContactData contactData) {
selectedContacts.put(contactData.id, contactData);
}
private void removeContact(ContactData contactData) {
selectedContacts.remove(contactData.id);
}
private void addMultipleNumberContact(ContactData contactData, TextView textView, CheckBox checkBox) {
String[] options = new String[contactData.numbers.size()];
int i = 0;
for (NumberData option : contactData.numbers) {
options[i++] = option.type + " " + option.number;
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ContactSelectionlistFragment_select_for + " " + contactData.name);
builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData));
builder.setPositiveButton(android.R.string.ok, new DiscriminatorFinishedListener(contactData, textView, checkBox));
builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView, checkBox));
builder.show();
}
private void initializeCursor() {
setListAdapter(new ContactSelectionListAdapter(getActivity(), null));
this.getLoaderManager().initLoader(0, null, this);
}
private void initializeResources() {
this.getListView().setFocusable(true);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
((ContactItemView)v).selected();
}
private class ContactSelectionListAdapter extends CursorAdapter {
public ContactSelectionListAdapter(Context context, Cursor c) {
super(context, c);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
ContactItemView view = new ContactItemView(context);
bindView(view, context, cursor);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
PushFilterCursorWrapper wrappedCursor = (PushFilterCursorWrapper) cursor;
boolean isPushUser = wrappedCursor.getPushCount() > wrappedCursor.getPosition();
ContactData contactData = ContactAccessor.getInstance().getContactData(context, cursor);
((ContactItemView)view).set(contactData, isPushUser);
}
}
private class ContactItemView extends RelativeLayout {
private ContactData contactData;
private CheckBox checkBox;
private TextView name;
private TextView number;
private TextView label;
public ContactItemView(Context context) {
super(context);
li.inflate(R.layout.push_contact_selection_list_item, this, true);
this.name = (TextView) findViewById(R.id.name);
this.number = (TextView) findViewById(R.id.number);
this.label = (TextView) findViewById(R.id.label);
this.checkBox = (CheckBox) findViewById(R.id.check_box);
}
public void selected() {
checkBox.toggle();
if (checkBox.isChecked()) {
if (contactData.numbers.size() == 1) addSingleNumberContact(contactData);
else addMultipleNumberContact(contactData, name, checkBox);
} else {
removeContact(contactData);
}
}
public void set(ContactData contactData, boolean isPushUser) {
this.contactData = contactData;
if (!isPushUser) {
this.name.setTextColor(0xa0000000);
this.number.setTextColor(0xa0000000);
this.checkBox.setVisibility(View.GONE);
} else {
this.name.setTextColor(0xff000000);
this.number.setTextColor(0xff000000);
this.checkBox.setVisibility(View.VISIBLE);
}
if (selectedContacts.containsKey(contactData.id))
this.checkBox.setChecked(true);
else
this.checkBox.setChecked(false);
this.name.setText(contactData.name);
if (contactData.numbers.isEmpty()) {
this.name.setEnabled(false);
this.number.setText("");
this.label.setText("");
} else {
this.number.setText(contactData.numbers.get(0).number);
this.label.setText(contactData.numbers.get(0).type);
}
}
}
private class DiscriminatorFinishedListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
private final ContactData contactData;
private final TextView textView;
private final CheckBox checkBox;
public DiscriminatorFinishedListener(ContactData contactData, TextView textView, CheckBox checkBox) {
this.contactData = contactData;
this.textView = textView;
this.checkBox = checkBox;
}
public void onClick(DialogInterface dialog, int which) {
ContactData selected = selectedContacts.get(contactData.id);
if (selected == null && textView != null) {
if (textView != null) checkBox.setChecked(false);
} else if (selected.numbers.size() == 0) {
selectedContacts.remove(selected.id);
if (textView != null) checkBox.setChecked(false);
}
if (textView == null)
((CursorAdapter) getListView().getAdapter()).notifyDataSetChanged();
}
public void onCancel(DialogInterface dialog) {
onClick(dialog, 0);
}
}
private class DiscriminatorClickedListener implements DialogInterface.OnMultiChoiceClickListener {
private final ContactData contactData;
public DiscriminatorClickedListener(ContactData contactData) {
this.contactData = contactData;
}
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
Log.w("ContactSelectionListActivity", "Got checked: " + isChecked);
ContactData existing = selectedContacts.get(contactData.id);
if (existing == null) {
Log.w("ContactSelectionListActivity", "No existing contact data, creating...");
if (!isChecked)
throw new AssertionError("We shouldn't be unchecking data that doesn't exist.");
existing = new ContactData(contactData.id, contactData.name);
selectedContacts.put(existing.id, existing);
}
NumberData selectedData = contactData.numbers.get(which);
if (!isChecked) existing.numbers.remove(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(new PushFilterCursorWrapper(cursor, getActivity()));
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
((CursorAdapter) getListAdapter()).changeCursor(null);
}
}

View File

@ -0,0 +1,106 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.os.Debug;
import android.provider.ContactsContract;
import android.util.Log;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.util.InvalidNumberException;
import java.util.List;
public class PushFilterCursorWrapper extends CursorWrapper {
private int[] pushIndex;
private int[] normalIndex;
private int count = 0;
private int pushCount = 0;
private int pos = 0;
private final ContactAccessor contactAccessor = ContactAccessor.getInstance();
/*
* Don't know of a better way to do this without a large filtering through the entire dataset at first
*/
public PushFilterCursorWrapper(Cursor cursor, Context context) {
super(cursor);
this.count = super.getCount();
this.pushIndex = new int[this.count];
this.normalIndex = new int[this.count];
int pushPos = 0;
int normalPos = 0;
for (int i = 0; i < this.count; i++) {
super.moveToPosition(i);
List<ContactAccessor.NumberData> numbers = contactAccessor.getContactData(context, cursor).numbers;
if (numbers.size() > 0) {
try {
if (Util.isPushTransport(context, Util.canonicalizeNumber(context, numbers.get(0).number)))
this.pushIndex[pushPos++] = i;
else
this.normalIndex[normalPos++] = i;
} catch (InvalidNumberException ine) {
}
}
}
this.pushCount = pushPos;
super.moveToFirst();
}
@Override
public boolean move(int offset) {
return this.moveToPosition(this.pos + offset);
}
@Override
public boolean moveToNext() {
return this.moveToPosition(this.pos + 1);
}
@Override
public boolean moveToPrevious() {
return this.moveToPosition(this.pos - 1);
}
@Override
public boolean moveToFirst() {
return this.moveToPosition(0);
}
@Override
public boolean moveToLast() {
return this.moveToPosition(this.count - 1);
}
private int getPostFilteredPosition(int preFilteredPosition) {
return preFilteredPosition < this.pushCount
? this.pushIndex[preFilteredPosition]
: this.normalIndex[preFilteredPosition - pushCount];
}
@Override
public boolean moveToPosition(int position) {
if (position >= this.count || position < 0)
return false;
pos = position;
return super.moveToPosition(getPostFilteredPosition(position));
}
@Override
public int getCount() {
return this.count;
}
public int getPushCount() {
return this.pushCount;
}
@Override
public int getPosition() {
return this.pos;
}
}

View File

@ -24,15 +24,25 @@ import android.os.Parcelable;
import android.util.Log;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.push.ContactTokenDetails;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.FutureTaskListener;
import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.ListenableFutureTask;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
import java.io.IOException;
import java.util.HashSet;
public class Recipient implements Parcelable, CanonicalRecipientAddress {
private final static String TAG = "Recipient";
public static final Parcelable.Creator<Recipient> CREATOR = new Parcelable.Creator<Recipient>() {
public Recipient createFromParcel(Parcel in) {
return new Recipient(in);
@ -152,7 +162,31 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(getNumber());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || ((Object) this).getClass() != o.getClass()) return false; // the Object casting is due to an Android Studio bug...
Recipient recipient = (Recipient) o;
if (contactUri != null ? !contactUri.equals(recipient.contactUri) : recipient.contactUri != null)
return false;
if (name != null ? !name.equals(recipient.name) : recipient.name != null) return false;
if (number != null ? !number.equals(recipient.number) : recipient.number != null) return false;
return true;
}
@Override
public int hashCode() {
int result = number != null ? number.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (contactUri != null ? contactUri.hashCode() : 0);
return result;
}
public static interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}
}

View File

@ -0,0 +1,72 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.ArrayList;
import java.util.List;
public class SelectedRecipientsAdapter extends ArrayAdapter<Recipient> {
private ArrayList<Recipient> recipients;
public SelectedRecipientsAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
}
public SelectedRecipientsAdapter(Context context, int resource, ArrayList<Recipient> recipients) {
super(context, resource, recipients);
this.recipients = recipients;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi;
vi = LayoutInflater.from(getContext());
v = vi.inflate(R.layout.selected_recipient_list_item, null);
}
Recipient p = getItem(position);
if (p != null) {
TextView name = (TextView) v.findViewById(R.id.name);
TextView phone = (TextView) v.findViewById(R.id.phone);
ImageButton delete = (ImageButton) v.findViewById(R.id.delete);
if (name != null) {
name.setText(p.getName());
}
if (phone != null) {
phone.setText(p.getNumber());
}
if (delete != null) {
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
recipients.remove(position);
SelectedRecipientsAdapter.this.notifyDataSetChanged();
}
});
}
}
return v;
}
}

View File

@ -28,6 +28,11 @@ import android.os.Build;
import android.provider.Telephony;
import org.thoughtcrime.securesms.mms.MmsRadio;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.push.ContactTokenDetails;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
@ -151,6 +156,31 @@ public class Util {
(context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context)));
}
public static boolean isPushTransport(Context context, String destination) {
Directory directory = Directory.getInstance(context);
try {
return directory.isActiveNumber(destination);
} catch (NotInDirectoryException e) {
try {
PushServiceSocket socket = PushServiceSocketFactory.create(context);
String contactToken = directory.getToken(destination);
ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken);
if (registeredUser == null) {
registeredUser = new ContactTokenDetails(contactToken);
directory.setToken(registeredUser, false);
return false;
} else {
directory.setToken(registeredUser, true);
return true;
}
} catch (IOException e1) {
Log.w("UniversalTransport", e1);
return false;
}
}
}
// public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
// return BitmapFactory.decodeStream(src);
//// BitmapFactory.Options options = new BitmapFactory.Options();