diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4ed58e13c9..983d6ec189 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -71,7 +71,7 @@
-
+
@@ -93,11 +93,18 @@
-
+
+
@@ -126,7 +133,7 @@
diff --git a/res/drawable/share_list_divider_shape.xml b/res/drawable/share_list_divider_shape.xml
new file mode 100644
index 0000000000..aa46fbd9f0
--- /dev/null
+++ b/res/drawable/share_list_divider_shape.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/share_list_divider_shape_dark.xml b/res/drawable/share_list_divider_shape_dark.xml
new file mode 100644
index 0000000000..d33a7d39ea
--- /dev/null
+++ b/res/drawable/share_list_divider_shape_dark.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index 95d339b634..0057771baf 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -6,12 +6,6 @@
android:background="?conversation_background"
android:orientation="vertical">
-
-
+
+
+
+
+
+
diff --git a/res/layout/share_fragment.xml b/res/layout/share_fragment.xml
new file mode 100644
index 0000000000..10f692b5a1
--- /dev/null
+++ b/res/layout/share_fragment.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/res/layout/share_list_item_view.xml b/res/layout/share_list_item_view.xml
new file mode 100644
index 0000000000..f9e7cf08c6
--- /dev/null
+++ b/res/layout/share_list_item_view.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/menu/share.xml b/res/menu/share.xml
new file mode 100644
index 0000000000..658c80ab4a
--- /dev/null
+++ b/res/menu/share.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4379a43aae..a295288deb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -9,6 +9,8 @@
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fc0c87bb7a..ad08d51d57 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -84,13 +84,12 @@
Sorry, the selected audio exceeds message size restrictions.
Recipient is not a valid SMS or email address!
Message is empty!
- FWD
Group Conversation Recipients
Group Conversation
Unnamed Group
%d members
1 member
- Saving draft...
+ Saved draft
Invalid recipient!
Calls Not Supported
This device does not appear to support dial actions.
@@ -116,6 +115,9 @@
Key exchange message...
+
+ Share with
+
Export To SD Card?
This
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 2dbc94b835..8215f68a53 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -14,6 +14,8 @@
- #ff999999
- @drawable/conversation_list_divider_shape
+ - @drawable/share_list_divider_shape
+
- @drawable/actionbar_icon_holo_light
- @drawable/divet_lower_right_dark
@@ -87,6 +89,8 @@
- #ffdddddd
- @drawable/conversation_list_divider_shape_dark
+ - @drawable/share_list_divider_shape_dark
+
- #99ffffff
- #ffeeeeee
- #44eeeeee
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index ace667525c..ddaf32c6ca 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -58,7 +58,6 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.components.EmojiDrawer;
import org.thoughtcrime.securesms.components.EmojiToggle;
-import org.thoughtcrime.securesms.components.RecipientsPanel;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
@@ -139,21 +138,18 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
public static final String DRAFT_VIDEO_EXTRA = "draft_video";
public static final String DISTRIBUTION_TYPE_EXTRA = "distribution_type";
- private static final int PICK_CONTACT = 1;
- private static final int PICK_IMAGE = 2;
- private static final int PICK_VIDEO = 3;
- private static final int PICK_AUDIO = 4;
- private static final int PICK_CONTACT_INFO = 5;
- private static final int GROUP_EDIT = 6;
+ private static final int PICK_IMAGE = 1;
+ private static final int PICK_VIDEO = 2;
+ private static final int PICK_AUDIO = 3;
+ private static final int PICK_CONTACT_INFO = 4;
+ private static final int GROUP_EDIT = 5;
private static final int SEND_ATTRIBUTES[] = new int[]{R.attr.conversation_send_button_push,
R.attr.conversation_send_button_sms_secure,
R.attr.conversation_send_button_sms_insecure};
private MasterSecret masterSecret;
- private RecipientsPanel recipientsPanel;
private EditText composeText;
- private ImageButton addContactButton;
private ImageButton sendButton;
private TextView charactersLeft;
@@ -200,7 +196,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
@Override
protected void onResume() {
- initializeRecipientsInput();
super.onResume();
dynamicTheme.onResume(this);
@@ -241,11 +236,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
if (data == null || resultCode != RESULT_OK) return;
switch (reqCode) {
- case PICK_CONTACT:
- Recipients recipients = data.getParcelableExtra("recipients");
- if (recipients != null)
- recipientsPanel.addRecipients(recipients);
- break;
case PICK_IMAGE:
addAttachmentImage(data.getData());
break;
@@ -723,12 +713,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
private void initializeResources() {
- recipientsPanel = (RecipientsPanel)findViewById(R.id.recipients);
recipients = RecipientFactory.getRecipientsForIds(this, getIntent().getStringExtra(RECIPIENTS_EXTRA), true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA,
ThreadDatabase.DistributionTypes.DEFAULT);
- addContactButton = (ImageButton)findViewById(R.id.contacts_button);
sendButton = (ImageButton)findViewById(R.id.send_button);
composeText = (EditText)findViewById(R.id.embedded_text_editor);
masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
@@ -746,10 +734,8 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
SendButtonListener sendButtonListener = new SendButtonListener();
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
- recipientsPanel.setPanelChangeListener(new RecipientsPanelChangeListener());
sendButton.setOnClickListener(sendButtonListener);
sendButton.setEnabled(true);
- addContactButton.setOnClickListener(new AddRecipientButtonListener());
composeText.setOnKeyListener(composeKeyPressedListener);
composeText.addTextChangedListener(composeKeyPressedListener);
composeText.setOnEditorActionListener(sendButtonListener);
@@ -769,23 +755,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && TextSecurePreferences.isScreenSecurityEnabled(this)) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
-
- if (getIntent().getStringExtra("forwarded_message") != null) {
- composeText.setText(getString(R.string.ConversationActivity_forward_message_prefix) + ": " +
- getIntent().getStringExtra("forwarded_message"));
- }
-
- }
-
- private void initializeRecipientsInput() {
- if (recipients == null || recipients.isEmpty()) {
- recipientsPanel.setVisibility(View.VISIBLE);
- } else if (recipients != null) {
- recipientsPanel.addRecipients(this.recipients);
- } else {
- InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- input.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
- }
}
private void initializeReceivers() {
@@ -945,9 +914,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
new AsyncTask() {
@Override
- protected void onPreExecute() {
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
Toast.makeText(ConversationActivity.this,
- R.string.ConversationActivity_saving_draft,
+ R.string.ConversationActivity_saved_draft,
Toast.LENGTH_SHORT).show();
}
@@ -1004,13 +974,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
private Recipients getRecipients() {
- try {
- if (isExistingConversation()) return this.recipients;
- else return recipientsPanel.getRecipients();
- } catch (RecipientFormattingException rfe) {
- Log.d(TAG, "Empty list of recipients retrieved from RecipientsPanel.");
- return null;
- }
+ return this.recipients;
}
private String getMessage() throws InvalidMessageException {
@@ -1038,7 +1002,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private void sendComplete(Recipients recipients, long threadId, boolean refreshFragment) {
attachmentManager.clear();
- recipientsPanel.disable();
composeText.setText("");
this.recipients = recipients;
@@ -1049,7 +1012,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
if (refreshFragment) {
fragment.reload(recipients, threadId);
- this.recipientsPanel.setVisibility(View.GONE);
initializeTitleBar();
initializeSecurity();
}
@@ -1114,14 +1076,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
// Listeners
- private class AddRecipientButtonListener implements OnClickListener {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(ConversationActivity.this, ContactSelectionActivity.class);
- startActivityForResult(intent, PICK_CONTACT);
- }
- }
-
private class AttachmentTypeListener implements DialogInterface.OnClickListener {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -1129,15 +1083,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
}
- private class RecipientsPanelChangeListener implements RecipientsPanel.RecipientsPanelChangedListener {
- @Override
- public void onRecipientsPanelUpdate(Recipients recipients) {
- initializeSecurity();
- initializeTitleBar();
- calculateCharactersRemaining();
- }
- }
-
private class EmojiToggleListener implements OnClickListener {
@Override
public void onClick(View v) {
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 8f2208a8cb..abae3631ff 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -184,9 +184,9 @@ public class ConversationFragment extends SherlockListFragment
}
private void handleForwardMessage(MessageRecord message) {
- Intent composeIntent = new Intent(getActivity(), ConversationActivity.class);
- composeIntent.putExtra("forwarded_message", message.getDisplayBody().toString());
- composeIntent.putExtra("master_secret", masterSecret);
+ Intent composeIntent = new Intent(getActivity(), ShareActivity.class);
+ composeIntent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, message.getDisplayBody().toString());
+ composeIntent.putExtra(ShareActivity.MASTER_SECRET_EXTRA, masterSecret);
startActivity(composeIntent);
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index ca4e7c06a1..a8598b04e2 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -64,7 +64,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
setContentView(R.layout.conversation_list_activity);
- ActionBarUtil.initializeDefaultActionBar(this, getSupportActionBar(), "TextSecure");
+ ActionBarUtil.initializeDefaultActionBar(this, getSupportActionBar(), R.string.app_name);
initializeNavigationDrawer();
initializeSenderReceiverService();
@@ -287,7 +287,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
this.drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
this.drawerList = (ListView)findViewById(R.id.left_drawer);
- this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
+ this.masterSecret = getIntent().getParcelableExtra("master_secret");
this.fragment = (ConversationListFragment)this.getSupportFragmentManager()
.findFragmentById(R.id.fragment_content);
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
index c0191edc9c..474faf7c9d 100644
--- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -1,3 +1,20 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
package org.thoughtcrime.securesms;
import android.app.Activity;
@@ -67,7 +84,11 @@ import ws.com.google.android.mms.MmsException;
import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
-
+/**
+ * Activity to create and update groups
+ *
+ * @author Jake McGinty
+ */
public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActivity {
private final static String TAG = GroupCreateActivity.class.getSimpleName();
diff --git a/src/org/thoughtcrime/securesms/NewConversationActivity.java b/src/org/thoughtcrime/securesms/NewConversationActivity.java
index 57ca481d78..64bd8a9d9f 100644
--- a/src/org/thoughtcrime/securesms/NewConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/NewConversationActivity.java
@@ -19,16 +19,12 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.Toast;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
-import org.thoughtcrime.securesms.components.SingleRecipientPanel;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
@@ -40,7 +36,6 @@ import org.thoughtcrime.securesms.util.ActionBarUtil;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.NumberUtil;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -159,6 +154,10 @@ public class NewConversationActivity extends PassphraseRequiredSherlockFragmentA
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.toIdString());
intent.putExtra(ConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
+ intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.DRAFT_TEXT_EXTRA));
+ intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, getIntent().getParcelableExtra(ConversationActivity.DRAFT_AUDIO_EXTRA));
+ intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, getIntent().getParcelableExtra(ConversationActivity.DRAFT_VIDEO_EXTRA));
+ intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, getIntent().getParcelableExtra(ConversationActivity.DRAFT_IMAGE_EXTRA));
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipients);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
diff --git a/src/org/thoughtcrime/securesms/RoutingActivity.java b/src/org/thoughtcrime/securesms/RoutingActivity.java
index 83c3675df5..711d237e0a 100644
--- a/src/org/thoughtcrime/securesms/RoutingActivity.java
+++ b/src/org/thoughtcrime/securesms/RoutingActivity.java
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -126,16 +127,25 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
}
private void handleDisplayConversationOrList() {
- ConversationParameters parameters = getConversationParameters();
+ final ConversationParameters parameters = getConversationParameters();
- Intent intent;
-
- if (isShareAction() || parameters.recipients != null) {
+ final Intent intent;
+ if (isShareAction()) {
+ intent = getShareIntent(parameters);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ } else if (parameters.recipients != null) {
intent = getConversationIntent(parameters);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
} else {
intent = getConversationListIntent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ }
}
-
startActivity(intent);
finish();
}
@@ -153,6 +163,20 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
return intent;
}
+ private Intent getShareIntent(ConversationParameters parameters) {
+ Intent intent = new Intent(this, ShareActivity.class);
+ intent.putExtra("master_secret", masterSecret);
+
+ if (parameters != null) {
+ intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, parameters.draftText);
+ intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, parameters.draftImage);
+ intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, parameters.draftAudio);
+ intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, parameters.draftVideo);
+ }
+
+ return intent;
+ }
+
private Intent getConversationListIntent() {
Intent intent = new Intent(this, ConversationListActivity.class);
intent.putExtra("master_secret", masterSecret);
@@ -197,8 +221,8 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
}
private ConversationParameters getConversationParametersForSendAction() {
- Recipients recipients = null;
- long threadId = getIntent().getLongExtra("thread_id", -1);
+ Recipients recipients;
+ long threadId = getIntent().getLongExtra("thread_id", -1);
try {
String data = getIntent().getData().getSchemeSpecificPart();
@@ -220,11 +244,11 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
if ("text/plain".equals(type)) {
draftText = getIntent().getStringExtra(Intent.EXTRA_TEXT);
- } else if (type.startsWith("image/")) {
+ } else if (type != null && type.startsWith("image/")) {
draftImage = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
- } else if (type.startsWith("audio/")) {
+ } else if (type != null && type.startsWith("audio/")) {
draftAudio = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
- } else if (type.startsWith("video/")) {
+ } else if (type != null && type.startsWith("video/")) {
draftVideo = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
}
diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java
new file mode 100644
index 0000000000..cba5b018c2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ShareActivity.java
@@ -0,0 +1,173 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.thoughtcrime.securesms;
+
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
+import org.thoughtcrime.securesms.util.ActionBarUtil;
+import org.thoughtcrime.securesms.util.DynamicLanguage;
+import org.thoughtcrime.securesms.util.DynamicTheme;
+import org.thoughtcrime.securesms.util.MemoryCleaner;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+
+/**
+ * An activity to quickly share content with contacts
+ *
+ * @author Jake McGinty
+ */
+public class ShareActivity extends PassphraseRequiredSherlockFragmentActivity
+ implements ShareFragment.ConversationSelectedListener
+ {
+ public final static String MASTER_SECRET_EXTRA = "master_secret";
+
+ private final DynamicTheme dynamicTheme = new DynamicTheme ();
+ private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
+
+ private ShareFragment fragment;
+ private MasterSecret masterSecret;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ dynamicTheme.onCreate(this);
+ dynamicLanguage.onCreate(this);
+ super.onCreate(icicle);
+
+ setContentView(R.layout.share_activity);
+ ActionBarUtil.initializeDefaultActionBar(this, getSupportActionBar(), R.string.ShareActivity_share_with);
+ initializeResources();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ dynamicTheme.onResume(this);
+ dynamicLanguage.onResume(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (!isFinishing()) finish();
+ }
+
+ @Override
+ public void onDestroy() {
+ MemoryCleaner.clean(masterSecret);
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuInflater inflater = this.getSupportMenuInflater();
+ menu.clear();
+
+ inflater.inflate(R.menu.share, menu);
+ super.onPrepareOptionsMenu(menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ super.onOptionsItemSelected(item);
+ switch (item.getItemId()) {
+ case R.id.menu_new_message: handleNewConversation(); return true;
+ case android.R.id.home: finish(); return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onMasterSecretCleared() {
+ startActivity(new Intent(this, RoutingActivity.class));
+ super.onMasterSecretCleared();
+ }
+
+ private void handleNewConversation() {
+ Intent intent = getBaseShareIntent(NewConversationActivity.class);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onCreateConversation(long threadId, Recipients recipients, int distributionType) {
+ createConversation(threadId, recipients, distributionType);
+ }
+
+ private void createConversation(long threadId, Recipients recipients, int distributionType) {
+ final Intent intent = getBaseShareIntent(ConversationActivity.class);
+ intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients.toIdString());
+ intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
+ intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
+
+ startActivity(intent);
+ }
+
+ private void initializeResources() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && TextSecurePreferences.isScreenSecurityEnabled(this)) {
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE);
+ }
+
+ this.masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
+
+ this.fragment = (ShareFragment)this.getSupportFragmentManager()
+ .findFragmentById(R.id.fragment_content);
+
+ this.fragment.setMasterSecret(masterSecret);
+ }
+
+ private Intent getBaseShareIntent(final Class> target) {
+ final Intent intent = new Intent(this, target);
+ final Intent originalIntent = getIntent();
+ final String draftText = originalIntent.getStringExtra(ConversationActivity.DRAFT_TEXT_EXTRA);
+ final Uri draftImage = originalIntent.getParcelableExtra(ConversationActivity.DRAFT_IMAGE_EXTRA);
+ final Uri draftAudio = originalIntent.getParcelableExtra(ConversationActivity.DRAFT_AUDIO_EXTRA);
+ final Uri draftVideo = originalIntent.getParcelableExtra(ConversationActivity.DRAFT_VIDEO_EXTRA);
+
+ intent.putExtra(ConversationActivity.DRAFT_TEXT_EXTRA, draftText);
+ intent.putExtra(ConversationActivity.DRAFT_IMAGE_EXTRA, draftImage);
+ intent.putExtra(ConversationActivity.DRAFT_AUDIO_EXTRA, draftAudio);
+ intent.putExtra(ConversationActivity.DRAFT_VIDEO_EXTRA, draftVideo);
+ intent.putExtra(NewConversationActivity.MASTER_SECRET_EXTRA, masterSecret);
+
+ return intent;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/ShareFragment.java b/src/org/thoughtcrime/securesms/ShareFragment.java
new file mode 100644
index 0000000000..320a8c6248
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ShareFragment.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+
+/**
+ * A fragment to select and share to open conversations
+ *
+ * @author Jake McGinty
+ */
+public class ShareFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks {
+
+ private ConversationSelectedListener listener;
+ private MasterSecret masterSecret;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ return inflater.inflate(R.layout.share_fragment, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle bundle) {
+ super.onActivityCreated(bundle);
+
+ initializeListAdapter();
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ this.listener = (ConversationSelectedListener) activity;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ if (v instanceof ShareListItem) {
+ ShareListItem headerView = (ShareListItem) v;
+
+ handleCreateConversation(headerView.getThreadId(), headerView.getRecipients(),
+ headerView.getDistributionType());
+ }
+ }
+
+ public void setMasterSecret(MasterSecret masterSecret) {
+ if (this.masterSecret != masterSecret) {
+ this.masterSecret = masterSecret;
+ initializeListAdapter();
+ }
+ }
+
+ private void initializeListAdapter() {
+ this.setListAdapter(new ShareListAdapter(getActivity(), null, masterSecret));
+ getListView().setRecyclerListener((ShareListAdapter) getListAdapter());
+ getLoaderManager().restartLoader(0, null, this);
+ }
+
+ private void handleCreateConversation(long threadId, Recipients recipients, int distributionType) {
+ listener.onCreateConversation(threadId, recipients, distributionType);
+ }
+
+ @Override
+ public Loader onCreateLoader(int arg0, Bundle arg1) {
+ return new ConversationListLoader(getActivity(), null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader arg0, Cursor cursor) {
+ ((CursorAdapter)getListAdapter()).changeCursor(cursor);
+ }
+
+ @Override
+ public void onLoaderReset(Loader arg0) {
+ ((CursorAdapter)getListAdapter()).changeCursor(null);
+ }
+
+ public interface ConversationSelectedListener {
+ public void onCreateConversation(long threadId, Recipients recipients, int distributionType);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/ShareListAdapter.java b/src/org/thoughtcrime/securesms/ShareListAdapter.java
new file mode 100644
index 0000000000..100a67b2cc
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ShareListAdapter.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.ThreadDatabase;
+import org.thoughtcrime.securesms.database.model.ThreadRecord;
+import org.whispersystems.textsecure.crypto.MasterCipher;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+
+/**
+ * A CursorAdapter for building a list of open conversations
+ *
+ * @author Jake McGinty
+ */
+public class ShareListAdapter extends CursorAdapter implements AbsListView.RecyclerListener {
+
+ private final ThreadDatabase threadDatabase;
+ private final MasterCipher masterCipher;
+ private final Context context;
+ private final LayoutInflater inflater;
+
+ public ShareListAdapter(Context context, Cursor cursor, MasterSecret masterSecret) {
+ super(context, cursor, 0);
+
+ if (masterSecret != null) this.masterCipher = new MasterCipher(masterSecret);
+ else this.masterCipher = null;
+
+ this.context = context;
+ this.threadDatabase = DatabaseFactory.getThreadDatabase(context);
+ this.inflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return inflater.inflate(R.layout.share_list_item_view, parent, false);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ if (masterCipher != null) {
+ ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor, masterCipher);
+ ThreadRecord record = reader.getCurrent();
+
+ ((ShareListItem)view).set(record);
+ }
+ }
+
+ @Override
+ public void onMovedToScrapHeap(View view) {
+ ((ShareListItem)view).unbind();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/ShareListItem.java b/src/org/thoughtcrime/securesms/ShareListItem.java
new file mode 100644
index 0000000000..119529ba9c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ShareListItem.java
@@ -0,0 +1,155 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.provider.Contacts.Intents;
+import android.provider.ContactsContract.QuickContact;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.database.model.ThreadRecord;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.BitmapUtil;
+import org.thoughtcrime.securesms.util.DateUtils;
+import org.thoughtcrime.securesms.util.Emoji;
+
+import java.util.Set;
+
+/**
+ * A simple view to show the recipients of an open conversation
+ *
+ * @author Jake McGinty
+ */
+public class ShareListItem extends RelativeLayout
+ implements Recipient.RecipientModifiedListener
+{
+ private final static String TAG = ShareListItem.class.getSimpleName();
+
+ private Context context;
+ private Recipients recipients;
+ private long threadId;
+ private TextView fromView;
+
+ private ImageView contactPhotoImage;
+
+ private final Handler handler = new Handler();
+ private int distributionType;
+
+ public ShareListItem(Context context) {
+ super(context);
+ this.context = context;
+ }
+
+ public ShareListItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ this.fromView = (TextView) findViewById(R.id.from);
+ this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image);
+ }
+
+ public void set(ThreadRecord thread) {
+ this.recipients = thread.getRecipients();
+ this.threadId = thread.getThreadId();
+ this.distributionType = thread.getDistributionType();
+
+ this.recipients.addListener(this);
+ this.fromView.setText(formatFrom(recipients));
+
+ setBackground();
+ setContactPhoto(this.recipients.getPrimaryRecipient());
+ }
+
+ public void unbind() {
+ if (this.recipients != null) this.recipients.removeListener(this);
+ }
+
+ private void setContactPhoto(final Recipient recipient) {
+ if (recipient == null) return;
+ contactPhotoImage.setImageBitmap(BitmapUtil.getCircleCroppedBitmap(recipient.getContactPhoto()));
+ }
+
+ private void setBackground() {
+ int[] attributes = new int[]{R.attr.conversation_list_item_background_read};
+ TypedArray drawables = context.obtainStyledAttributes(attributes);
+
+ setBackgroundDrawable(drawables.getDrawable(0));
+
+ drawables.recycle();
+ }
+
+ private CharSequence formatFrom(Recipients from) {
+ final String fromString;
+ final boolean isUnnamedGroup = from.isGroupRecipient() && TextUtils.isEmpty(from.getPrimaryRecipient().getName());
+ if (isUnnamedGroup) {
+ fromString = context.getString(R.string.ConversationActivity_unnamed_group);
+ } else {
+ fromString = from.toShortString();
+ }
+ SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
+
+ final int typeface;
+ if (isUnnamedGroup) typeface = Typeface.ITALIC;
+ else typeface = Typeface.NORMAL;
+
+ builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ return builder;
+ }
+
+ public Recipients getRecipients() {
+ return recipients;
+ }
+
+ public long getThreadId() {
+ return threadId;
+ }
+
+ public int getDistributionType() {
+ return distributionType;
+ }
+
+ @Override
+ public void onModified(Recipient recipient) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ fromView.setText(formatFrom(recipients));
+ setContactPhoto(recipients.getPrimaryRecipient());
+ }
+ });
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/RecipientsPanel.java b/src/org/thoughtcrime/securesms/components/RecipientsPanel.java
deleted file mode 100644
index 40f29c5206..0000000000
--- a/src/org/thoughtcrime/securesms/components/RecipientsPanel.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * Copyright (C) 2011 Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.thoughtcrime.securesms.components;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.RelativeLayout;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.contacts.ContactAccessor;
-import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
-import org.thoughtcrime.securesms.contacts.RecipientsEditor;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.RecipientFactory;
-import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
-import org.thoughtcrime.securesms.recipients.Recipients;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Panel component combining both an editable field with a button for
- * a list-based contact selector.
- *
- * @author Moxie Marlinspike
- */
-public class RecipientsPanel extends RelativeLayout {
-
- private RecipientsPanelChangedListener panelChangeListener;
- private RecipientsEditor recipientsText;
- private View panel;
-
- private static final int RECIPIENTS_MAX_LENGTH = 312;
-
- public RecipientsPanel(Context context) {
- super(context);
- initialize();
- }
-
- public RecipientsPanel(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
- }
-
- public RecipientsPanel(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize();
- }
-
- public void addRecipient(String name, String number) {
- if (name != null) recipientsText.append(sanitizeRecipientName(name) + "< " + number + ">, ");
- else recipientsText.append(number + ", ");
- }
-
- public void addContacts(List contacts) {
- for (ContactAccessor.ContactData contact : contacts) {
- for (ContactAccessor.NumberData number : contact.numbers) {
- addRecipient(contact.name, number.number);
- }
- }
- }
-
- public void addRecipients(Recipients recipients) {
- Set panelRecipients;
-
- try {
- panelRecipients = new HashSet(getRecipients().getRecipientsList());
- } catch (RecipientFormattingException e) {
- Log.w("RecipientsPanel", e);
- panelRecipients = new HashSet();
- }
-
- List recipientList = recipients.getRecipientsList();
- Iterator iterator = recipientList.iterator();
-
- while (iterator.hasNext()) {
- Recipient recipient = iterator.next();
- if (!panelRecipients.contains(recipient)) {
- addRecipient(recipient.getName(), recipient.getNumber());
- }
- }
- }
-
- public Recipients getRecipients() throws RecipientFormattingException {
- String rawText = recipientsText.getText().toString();
- Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), rawText, false);
-
- if (recipients.isEmpty())
- throw new RecipientFormattingException("Recipient List Is Empty!");
-
- return recipients;
- }
-
- public void disable() {
- recipientsText.setText("");
- panel.setVisibility(View.GONE);
- }
-
- public void setPanelChangeListener(RecipientsPanelChangedListener panelChangeListener) {
- this.panelChangeListener = panelChangeListener;
- }
-
- private void initialize() {
- LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.recipients_panel, this, true);
-
- View imageButton = findViewById(R.id.contacts_button);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- ((MarginLayoutParams) imageButton.getLayoutParams()).topMargin = 0;
-
- panel = findViewById(R.id.recipients_panel);
- initRecipientsEditor();
- }
-
- private void initRecipientsEditor() {
- Recipients recipients = null;
- recipientsText = (RecipientsEditor)findViewById(R.id.recipients_text);
-
- try {
- recipients = getRecipients();
- } catch (RecipientFormattingException e) {
- recipients = new Recipients( new LinkedList() );
- }
-
- recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));
- recipientsText.populate(recipients);
- recipientsText.setOnFocusChangeListener(new FocusChangedListener());
- }
-
- private static String sanitizeRecipientName(String name) {
- return name.replaceAll("[,<>]", "");
- }
-
- private class FocusChangedListener implements View.OnFocusChangeListener {
- public void onFocusChange(View v, boolean hasFocus) {
- if (!hasFocus && (panelChangeListener != null)) {
- try {
- panelChangeListener.onRecipientsPanelUpdate(getRecipients());
- } catch (RecipientFormattingException rfe) {
- panelChangeListener.onRecipientsPanelUpdate(null);
- }
- }
- }
- }
-
- public interface RecipientsPanelChangedListener {
- public void onRecipientsPanelUpdate(Recipients recipients);
- }
-
-}
diff --git a/src/org/thoughtcrime/securesms/util/EncryptedCharacterCalculator.java b/src/org/thoughtcrime/securesms/util/EncryptedCharacterCalculator.java
index 00ce9b054f..9761d96f57 100644
--- a/src/org/thoughtcrime/securesms/util/EncryptedCharacterCalculator.java
+++ b/src/org/thoughtcrime/securesms/util/EncryptedCharacterCalculator.java
@@ -16,8 +16,6 @@
*/
package org.thoughtcrime.securesms.util;
-import android.util.Log;
-
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
public class EncryptedCharacterCalculator extends CharacterCalculator {
@@ -37,7 +35,6 @@ public class EncryptedCharacterCalculator extends CharacterCalculator {
spilloverMessagesSpent++;
int charactersRemaining = (SmsTransportDetails.MULTI_MESSAGE_MAX_BYTES * spilloverMessagesSpent) - spillover;
- Log.w("EncryptedCharacterCalculator", "charactersRemaining: " + charactersRemaining);
return new CharacterState(spilloverMessagesSpent+1, charactersRemaining, SmsTransportDetails.MULTI_MESSAGE_MAX_BYTES);
}