transport selection refactor

Closes #1724
// FREEBIE
This commit is contained in:
Jake McGinty
2014-07-18 19:31:03 -07:00
parent 4a088410ae
commit 30232c1635
13 changed files with 446 additions and 96 deletions

View File

@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
@@ -34,18 +33,15 @@ import android.provider.ContactsContract;
import android.telephony.PhoneNumberUtils;
import android.text.Editable;
import android.text.InputType;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
@@ -53,7 +49,6 @@ import android.view.View.OnKeyListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
@@ -61,6 +56,7 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.components.EmojiDrawer;
import org.thoughtcrime.securesms.components.EmojiToggle;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
@@ -113,6 +109,7 @@ import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.api.push.PushAddress;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@@ -147,14 +144,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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 EditText composeText;
private ImageButton sendButton;
private TextView charactersLeft;
private MasterSecret masterSecret;
private EditText composeText;
private SendButton sendButton;
private TextView charactersLeft;
private AttachmentTypeSelectorAdapter attachmentAdapter;
private AttachmentManager attachmentManager;
@@ -317,49 +310,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return false;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (isEncryptedConversation && isSingleConversation()) {
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
boolean isSecureDestination = isSingleConversation() && sessionStore.containsSession(primaryRecipient.getRecipientId(),
PushAddress.DEFAULT_DEVICE_ID);
getMenuInflater().inflate(R.menu.conversation_button_context, menu);
if (attachmentManager.isAttachmentPresent()) {
menu.removeItem(R.id.menu_context_send_encrypted_sms);
menu.removeItem(R.id.menu_context_send_unencrypted_sms);
} else {
menu.removeItem(R.id.menu_context_send_encrypted_mms);
menu.removeItem(R.id.menu_context_send_unencrypted_mms);
}
if (!isPushDestination) {
menu.removeItem(R.id.menu_context_send_push);
}
if (!isSecureDestination) {
menu.removeItem(R.id.menu_context_send_encrypted_mms);
menu.removeItem(R.id.menu_context_send_encrypted_sms);
}
}
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_context_send_push: sendMessage(false, false); return true;
case R.id.menu_context_send_encrypted_mms:
case R.id.menu_context_send_encrypted_sms: sendMessage(false, true); return true;
case R.id.menu_context_send_unencrypted_mms:
case R.id.menu_context_send_unencrypted_sms: sendMessage(true, true); return true;
}
return false;
}
@Override
public void onBackPressed() {
if (emojiDrawer.getVisibility() == View.VISIBLE) {
@@ -700,7 +650,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeSecurity() {
TypedArray drawables = obtainStyledAttributes(SEND_ATTRIBUTES);
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
@@ -715,27 +664,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
this.characterCalculator = new CharacterCalculator();
}
if (isPushDestination) {
sendButton.setImageDrawable(drawables.getDrawable(0));
setComposeHint(getString(R.string.conversation_activity__type_message_push));
} else if (isSecureDestination) {
sendButton.setImageDrawable(drawables.getDrawable(1));
setComposeHint(attachmentManager.isAttachmentPresent() ?
getString(R.string.conversation_activity__type_message_mms_secure) :
getString(R.string.conversation_activity__type_message_sms_secure));
} else {
sendButton.setImageDrawable(drawables.getDrawable(2));
setComposeHint((attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient()) ?
getString(R.string.conversation_activity__type_message_mms_insecure) :
getString(R.string.conversation_activity__type_message_sms_insecure));
}
sendButton.initializeAvailableTransports(!recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent());
if (!isPushDestination ) sendButton.disableTransport("textsecure");
if (!isSecureDestination) sendButton.disableTransport("secure_sms");
drawables.recycle();
if (isPushDestination) {
sendButton.setDefaultTransport("textsecure");
} else if (isSecureDestination) {
sendButton.setDefaultTransport("secure_sms");
} else {
sendButton.setDefaultTransport("insecure_sms");
}
calculateCharactersRemaining();
}
private void initializeMmsEnabledCheck() {
new AsyncTask<Void, Void, Boolean>() {
@Override
@@ -763,7 +706,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA,
ThreadDatabase.DistributionTypes.DEFAULT);
sendButton = (ImageButton)findViewById(R.id.send_button);
sendButton = (SendButton)findViewById(R.id.send_button);
composeText = (EditText)findViewById(R.id.embedded_text_editor);
masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
charactersLeft = (TextView)findViewById(R.id.space_left);
@@ -782,6 +725,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendButton.setOnClickListener(sendButtonListener);
sendButton.setEnabled(true);
sendButton.setComposeTextView(composeText);
composeText.setOnKeyListener(composeKeyPressedListener);
composeText.addTextChangedListener(composeKeyPressedListener);
composeText.setOnEditorActionListener(sendButtonListener);
@@ -796,8 +740,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
initializeTitleBar();
}
});
registerForContextMenu(sendButton);
}
private void initializeReceivers() {
@@ -1056,20 +998,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
fragment.scrollToBottom();
}
private void sendMessage(boolean forcePlaintext, boolean forceSms) {
private void sendMessage() {
try {
final Recipients recipients = getRecipients();
if (recipients == null)
if (recipients == null) {
throw new RecipientFormattingException("Badly formatted");
}
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
handleManualMmsRequired();
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
sendMediaMessage(forcePlaintext, forceSms);
sendMediaMessage(sendButton.getSelectedTransport().isForcedPlaintext(), sendButton.getSelectedTransport().isForcedSms());
} else {
sendTextMessage(forcePlaintext, forceSms);
sendTextMessage(sendButton.getSelectedTransport().isForcedPlaintext(), sendButton.getSelectedTransport().isForcedSms());
}
} catch (RecipientFormattingException ex) {
Toast.makeText(ConversationActivity.this,
@@ -1171,7 +1113,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
@Override
public void onClick(View v) {
sendMessage(false, false);
sendMessage();
}
@Override
@@ -1239,17 +1181,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
this.composeText.setText(text);
}
private void setComposeHint(String hint){
if (hint == null) {
this.composeText.setHint(null);
} else {
SpannableString span = new SpannableString(hint);
span.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
this.composeText.setHint(span);
this.sendButton.setContentDescription(hint);
}
}
@Override
public void onAttachmentChanged() {
initializeSecurity();

View File

@@ -0,0 +1,24 @@
package org.thoughtcrime.securesms;
public class TransportOption {
public int drawable;
public String text;
public String key;
public String composeHint;
public TransportOption(String key, int drawable, String text, String composeHint) {
this.key = key;
this.drawable = drawable;
this.text = text;
this.composeHint = composeHint;
}
public boolean isForcedPlaintext() {
return key.equals("insecure_sms");
}
public boolean isForcedSms() {
return key.equals("insecure_sms") || key.equals("secure_sms");
}
}

View File

@@ -0,0 +1,148 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.BitmapDrawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.PopupWindow;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class TransportOptions {
private static final String TAG = TransportOptions.class.getSimpleName();
private final Context context;
private PopupWindow transportPopup;
private final List<String> enabledTransports = new ArrayList<String>();
private final Map<String, TransportOption> transportMetadata = new HashMap<String, TransportOption>();
private String selectedTransport;
private boolean transportOverride = false;
private OnTransportChangedListener listener;
public TransportOptions(Context context) {
this.context = context;
}
private void initializeTransportPopup() {
if (transportPopup == null) {
final View selectionMenu = LayoutInflater.from(context).inflate(R.layout.transport_selection, null);
final ListView list = (ListView) selectionMenu.findViewById(R.id.transport_selection_list);
final TransportOptionsAdapter adapter = new TransportOptionsAdapter(context, enabledTransports, transportMetadata);
list.setAdapter(adapter);
transportPopup = new PopupWindow(selectionMenu);
transportPopup.setFocusable(true);
transportPopup.setBackgroundDrawable(new BitmapDrawable(context.getResources(), ""));
transportPopup.setOutsideTouchable(true);
transportPopup.setWindowLayoutMode(0, WindowManager.LayoutParams.WRAP_CONTENT);
transportPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
list.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
transportOverride = true;
setTransport((TransportOption) adapter.getItem(position));
transportPopup.dismiss();
}
});
} else {
final ListView list = (ListView) transportPopup.getContentView().findViewById(R.id.transport_selection_list);
final TransportOptionsAdapter adapter = (TransportOptionsAdapter) list.getAdapter();
adapter.setEnabledTransports(enabledTransports);
adapter.notifyDataSetInvalidated();
}
}
public void initializeAvailableTransports(boolean isMediaMessage) {
String[] entryArray = (isMediaMessage)
? context.getResources().getStringArray(R.array.transport_selection_entries_media)
: context.getResources().getStringArray(R.array.transport_selection_entries_text);
String[] composeHintArray = (isMediaMessage)
? context.getResources().getStringArray(R.array.transport_selection_entries_compose_media)
: context.getResources().getStringArray(R.array.transport_selection_entries_compose_text);
final String[] valuesArray = context.getResources().getStringArray(R.array.transport_selection_values);
final int[] attrs = new int[]{R.attr.conversation_transport_indicators};
final TypedArray iconArray = context.obtainStyledAttributes(attrs);
final int iconArrayResource = iconArray.getResourceId(0, -1);
final TypedArray icons = context.getResources().obtainTypedArray(iconArrayResource);
enabledTransports.clear();
for (int i=0; i<valuesArray.length; i++) {
String key = valuesArray[i];
enabledTransports.add(key);
transportMetadata.put(key, new TransportOption(key, icons.getResourceId(i, -1), entryArray[i], composeHintArray[i]));
}
iconArray.recycle();
icons.recycle();
updateViews();
}
public void setTransport(String transport) {
selectedTransport = transport;
updateViews();
}
private void setTransport(TransportOption transport) {
setTransport(transport.key);
}
public void showPopup(View parent) {
initializeTransportPopup();
transportPopup.showAsDropDown(parent,
context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff),
context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff));
}
public void setDefaultTransport(String transportName) {
if (!transportOverride) {
setTransport(transportName);
}
}
public TransportOption getSelectedTransport() {
return transportMetadata.get(selectedTransport);
}
public void disableTransport(String transportName) {
enabledTransports.remove(transportName);
}
public List<String> getEnabledTransports() {
return enabledTransports;
}
private void updateViews() {
if (selectedTransport == null) return;
if (listener != null) {
listener.onChange(getSelectedTransport());
}
}
public void setOnTransportChangedListener(OnTransportChangedListener listener) {
this.listener = listener;
}
public interface OnTransportChangedListener {
public void onChange(TransportOption newTransport);
}
}

View File

@@ -0,0 +1,71 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import java.util.Map;
public class TransportOptionsAdapter extends BaseAdapter {
private final Context context;
private final LayoutInflater inflater;
private List<String> enabledTransports;
private final Map<String, TransportOption> transportMetadata;
public TransportOptionsAdapter(final Context context,
final List<String> enabledTransports,
final Map<String, TransportOption> transportMetadata) {
super();
this.context = context;
this.inflater = LayoutInflater.from(context);
this.enabledTransports = enabledTransports;
this.transportMetadata = transportMetadata;
}
public TransportOptionsAdapter(final Context context,
final Map<String, TransportOption> transportMetadata) {
this(context, null, transportMetadata);
}
public void setEnabledTransports(final List<String> enabledTransports) {
this.enabledTransports = enabledTransports;
}
@Override
public int getCount() {
return enabledTransports == null ? 0 : enabledTransports.size();
}
@Override
public Object getItem(int position) {
return transportMetadata.get(enabledTransports.get(position));
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view;
if (convertView == null) {
view = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
} else {
view = convertView;
}
TransportOption transport = (TransportOption) getItem(position);
final ImageView imageView = (ImageView)view.findViewById(R.id.icon);
final TextView textView = (TextView) view.findViewById(R.id.text);
imageView.setImageResource(transport.drawable);
textView.setText(transport.text);
return view;
}
}

View File

@@ -0,0 +1,90 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.TransportOptions;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
public class SendButton extends ImageButton {
private TransportOptions transportOptions;
private EditText composeText;
@SuppressWarnings("unused")
public SendButton(Context context) {
super(context);
initialize();
}
@SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
@SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
transportOptions = new TransportOptions(getContext());
transportOptions.setOnTransportChangedListener(new OnTransportChangedListener() {
@Override
public void onChange(TransportOption newTransport) {
setImageResource(newTransport.drawable);
setContentDescription(newTransport.composeHint);
if (composeText != null) setComposeTextHint(newTransport.composeHint);
}
});
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (transportOptions.getEnabledTransports().size() > 1) {
transportOptions.showPopup(SendButton.this);
return true;
}
return false;
}
});
}
public void setComposeTextView(EditText composeText) {
this.composeText = composeText;
}
public TransportOption getSelectedTransport() {
return transportOptions.getSelectedTransport();
}
public void initializeAvailableTransports(boolean isMediaMessage) {
transportOptions.initializeAvailableTransports(isMediaMessage);
}
public void disableTransport(String transport) {
transportOptions.disableTransport(transport);
}
public void setDefaultTransport(String transport) {
transportOptions.setDefaultTransport(transport);
}
private void setComposeTextHint(String hint) {
if (hint == null) {
this.composeText.setHint(null);
} else {
SpannableString span = new SpannableString(hint);
span.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
this.composeText.setHint(span);
}
}
}