diff --git a/app/src/main/java/org/thoughtcrime/securesms/TransportOption.java b/app/src/main/java/org/thoughtcrime/securesms/TransportOption.java deleted file mode 100644 index a8afc974d8..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/TransportOption.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.util.CharacterCalculator; -import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState; - -import java.util.Optional; - - -public class TransportOption implements Parcelable { - - public enum Type { - SMS, - TEXTSECURE - } - - private final int drawable; - private final int backgroundColor; - private final @NonNull String text; - private final @NonNull Type type; - private final @NonNull String composeHint; - private final @NonNull CharacterCalculator characterCalculator; - private final @NonNull Optional simName; - private final @NonNull Optional simSubscriptionId; - - public TransportOption(@NonNull Type type, - @DrawableRes int drawable, - int backgroundColor, - @NonNull String text, - @NonNull String composeHint, - @NonNull CharacterCalculator characterCalculator) - { - this(type, drawable, backgroundColor, text, composeHint, characterCalculator, - Optional.empty(), Optional.empty()); - } - - public TransportOption(@NonNull Type type, - @DrawableRes int drawable, - int backgroundColor, - @NonNull String text, - @NonNull String composeHint, - @NonNull CharacterCalculator characterCalculator, - @NonNull Optional simName, - @NonNull Optional simSubscriptionId) - { - this.type = type; - this.drawable = drawable; - this.backgroundColor = backgroundColor; - this.text = text; - this.composeHint = composeHint; - this.characterCalculator = characterCalculator; - this.simName = simName; - this.simSubscriptionId = simSubscriptionId; - } - - TransportOption(Parcel in) { - this(Type.valueOf(in.readString()), - in.readInt(), - in.readInt(), - in.readString(), - in.readString(), - CharacterCalculator.readFromParcel(in), - Optional.ofNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)), - in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.empty()); - } - - public @NonNull Type getType() { - return type; - } - - public boolean isType(Type type) { - return this.type == type; - } - - public boolean isSms() { - return type == Type.SMS; - } - - public CharacterState calculateCharacters(String messageBody) { - return characterCalculator.calculateCharacters(messageBody); - } - - public @DrawableRes int getDrawable() { - return drawable; - } - - public int getBackgroundColor() { - return backgroundColor; - } - - public @NonNull String getComposeHint() { - return composeHint; - } - - public @NonNull String getDescription() { - return text; - } - - @NonNull - public Optional getSimName() { - return simName; - } - - @NonNull - public Optional getSimSubscriptionId() { - return simSubscriptionId; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(type.name()); - dest.writeInt(drawable); - dest.writeInt(backgroundColor); - dest.writeString(text); - dest.writeString(composeHint); - CharacterCalculator.writeToParcel(dest, characterCalculator); - TextUtils.writeToParcel(simName.orElse(null), dest, flags); - - if (simSubscriptionId.isPresent()) { - dest.writeInt(1); - dest.writeInt(simSubscriptionId.get()); - } else { - dest.writeInt(0); - } - } - - public static final Creator CREATOR = new Creator() { - @Override - public TransportOption createFromParcel(Parcel in) { - return new TransportOption(in); - } - - @Override - public TransportOption[] newArray(int size) { - return new TransportOption[size]; - } - }; -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java b/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java deleted file mode 100644 index 6d0058ac2b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/TransportOptions.java +++ /dev/null @@ -1,226 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.Manifest; -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.CharacterCalculator; -import org.thoughtcrime.securesms.util.MmsCharacterCalculator; -import org.thoughtcrime.securesms.util.PushCharacterCalculator; -import org.thoughtcrime.securesms.util.SmsCharacterCalculator; -import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat; -import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat; -import org.whispersystems.signalservice.api.util.OptionalUtil; - -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; - -import static org.thoughtcrime.securesms.TransportOption.Type; - -public class TransportOptions { - - private static final String TAG = Log.tag(TransportOptions.class); - - private final List listeners = new LinkedList<>(); - private final Context context; - private final List enabledTransports; - - private Type defaultTransportType = Type.SMS; - private Optional defaultSubscriptionId = Optional.empty(); - private Optional selectedOption = Optional.empty(); - - private final Optional systemSubscriptionId; - - public TransportOptions(Context context, boolean media) { - this.context = context; - this.enabledTransports = initializeAvailableTransports(media); - this.systemSubscriptionId = new SubscriptionManagerCompat(context).getPreferredSubscriptionId(); - } - - public void reset(boolean media) { - List transportOptions = initializeAvailableTransports(media); - - this.enabledTransports.clear(); - this.enabledTransports.addAll(transportOptions); - - if (selectedOption.isPresent() && !isEnabled(selectedOption.get())) { - setSelectedTransport(null); - } else { - this.defaultTransportType = Type.SMS; - this.defaultSubscriptionId = Optional.empty(); - - notifyTransportChangeListeners(); - } - } - - public void setDefaultTransport(Type type) { - this.defaultTransportType = type; - - if (!selectedOption.isPresent()) { - notifyTransportChangeListeners(); - } - } - - public void setDefaultSubscriptionId(Optional subscriptionId) { - if (defaultSubscriptionId.equals(subscriptionId)) { - return; - } - - this.defaultSubscriptionId = subscriptionId; - - if (!selectedOption.isPresent()) { - notifyTransportChangeListeners(); - } - } - - public void setSelectedTransport(@Nullable TransportOption transportOption) { - this.selectedOption = Optional.ofNullable(transportOption); - notifyTransportChangeListeners(); - } - - public boolean isManualSelection() { - return this.selectedOption.isPresent(); - } - - public @NonNull TransportOption getSelectedTransport() { - if (selectedOption.isPresent()) return selectedOption.get(); - - if (defaultTransportType == Type.SMS) { - TransportOption transportOption = findEnabledSmsTransportOption(OptionalUtil.or(defaultSubscriptionId, systemSubscriptionId)); - if (transportOption != null) { - return transportOption; - } - } - - for (TransportOption transportOption : enabledTransports) { - if (transportOption.getType() == defaultTransportType) { - return transportOption; - } - } - - throw new AssertionError("No options of default type!"); - } - - public static @NonNull TransportOption getPushTransportOption(@NonNull Context context) { - return new TransportOption(Type.TEXTSECURE, - R.drawable.ic_send_lock_24, - context.getResources().getColor(R.color.core_ultramarine), - context.getString(R.string.ConversationActivity_transport_signal), - context.getString(R.string.conversation_activity__type_message_push), - new PushCharacterCalculator()); - - } - - private @Nullable TransportOption findEnabledSmsTransportOption(Optional subscriptionId) { - if (subscriptionId.isPresent()) { - final int subId = subscriptionId.get(); - - for (TransportOption transportOption : enabledTransports) { - if (transportOption.getType() == Type.SMS && - subId == transportOption.getSimSubscriptionId().orElse(-1)) { - return transportOption; - } - } - } - return null; - } - - public void disableTransport(Type type) { - TransportOption selected = selectedOption.orElse(null); - - Iterator iterator = enabledTransports.iterator(); - while (iterator.hasNext()) { - TransportOption option = iterator.next(); - - if (option.isType(type)) { - if (selected == option) { - setSelectedTransport(null); - } - iterator.remove(); - } - } - } - - public List getEnabledTransports() { - return enabledTransports; - } - - public void addOnTransportChangedListener(OnTransportChangedListener listener) { - this.listeners.add(listener); - } - - private List initializeAvailableTransports(boolean isMediaMessage) { - List results = new LinkedList<>(); - - if (isMediaMessage) { - results.addAll(getTransportOptionsForSimCards(context.getString(R.string.ConversationActivity_transport_insecure_mms), - context.getString(R.string.conversation_activity__type_message_mms_insecure), - new MmsCharacterCalculator())); - } else { - results.addAll(getTransportOptionsForSimCards(context.getString(R.string.ConversationActivity_transport_insecure_sms), - context.getString(R.string.conversation_activity__type_message_sms_insecure), - new SmsCharacterCalculator())); - } - - results.add(getPushTransportOption(context)); - - return results; - } - - private @NonNull List getTransportOptionsForSimCards(@NonNull String text, - @NonNull String composeHint, - @NonNull CharacterCalculator characterCalculator) - { - List results = new LinkedList<>(); - SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context); - Collection subscriptions; - - if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE)) { - subscriptions = subscriptionManager.getActiveAndReadySubscriptionInfos(); - } else { - subscriptions = Collections.emptyList(); - } - - if (subscriptions.size() < 2) { - results.add(new TransportOption(Type.SMS, R.drawable.ic_send_unlock_24, - context.getResources().getColor(R.color.core_grey_50), - text, composeHint, characterCalculator)); - } else { - for (SubscriptionInfoCompat subscriptionInfo : subscriptions) { - results.add(new TransportOption(Type.SMS, R.drawable.ic_send_unlock_24, - context.getResources().getColor(R.color.core_grey_50), - text, composeHint, characterCalculator, - Optional.of(subscriptionInfo.getDisplayName()), - Optional.of(subscriptionInfo.getSubscriptionId()))); - } - } - - return results; - } - - private void notifyTransportChangeListeners() { - for (OnTransportChangedListener listener : listeners) { - listener.onChange(getSelectedTransport(), selectedOption.isPresent()); - } - } - - private boolean isEnabled(TransportOption transportOption) { - for (TransportOption option : enabledTransports) { - if (option.equals(transportOption)) return true; - } - - return false; - } - - public interface OnTransportChangedListener { - public void onChange(TransportOption newTransport, boolean manuallySelected); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/TransportOptionsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/TransportOptionsAdapter.java deleted file mode 100644 index b7bfaa44dd..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/TransportOptionsAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.content.Context; -import android.graphics.PorterDuff.Mode; -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 androidx.annotation.NonNull; - -import java.util.List; - -public class TransportOptionsAdapter extends BaseAdapter { - - private final LayoutInflater inflater; - - private List enabledTransports; - - public TransportOptionsAdapter(@NonNull Context context, - @NonNull List enabledTransports) - { - super(); - this.inflater = LayoutInflater.from(context); - this.enabledTransports = enabledTransports; - } - - public void setEnabledTransports(List enabledTransports) { - this.enabledTransports = enabledTransports; - } - - @Override - public int getCount() { - return enabledTransports.size(); - } - - @Override - public Object getItem(int position) { - return enabledTransports.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = inflater.inflate(R.layout.transport_selection_list_item, parent, false); - } - - TransportOption transport = (TransportOption) getItem(position); - ImageView imageView = convertView.findViewById(R.id.icon); - TextView textView = convertView.findViewById(R.id.text); - TextView subtextView = convertView.findViewById(R.id.subtext); - - imageView.getBackground().setColorFilter(transport.getBackgroundColor(), Mode.MULTIPLY); - imageView.setImageResource(transport.getDrawable()); - textView.setText(transport.getDescription()); - - if (transport.getSimName().isPresent()) { - subtextView.setText(transport.getSimName().get()); - subtextView.setVisibility(View.VISIBLE); - } else { - subtextView.setVisibility(View.GONE); - } - - return convertView; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/TransportOptionsPopup.java b/app/src/main/java/org/thoughtcrime/securesms/TransportOptionsPopup.java deleted file mode 100644 index 63e8096f10..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/TransportOptionsPopup.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.content.Context; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.ListPopupWindow; - -import java.util.LinkedList; -import java.util.List; - -public class TransportOptionsPopup extends ListPopupWindow implements ListView.OnItemClickListener { - - private final TransportOptionsAdapter adapter; - private final SelectedListener listener; - - public TransportOptionsPopup(@NonNull Context context, @NonNull View anchor, @NonNull SelectedListener listener) { - super(context); - this.listener = listener; - this.adapter = new TransportOptionsAdapter(context, new LinkedList()); - - setVerticalOffset(context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff)); - setHorizontalOffset(context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff)); - setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); - setModal(true); - setAnchorView(anchor); - setAdapter(adapter); - setContentWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width)); - - setOnItemClickListener(this); - } - - public void display(List enabledTransports) { - adapter.setEnabledTransports(enabledTransports); - adapter.notifyDataSetChanged(); - show(); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - listener.onSelected((TransportOption)adapter.getItem(position)); - } - - public interface SelectedListener { - void onSelected(TransportOption option); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java index 83bc5cc999..1c654f5d17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java @@ -28,12 +28,12 @@ import androidx.core.view.inputmethod.InputContentInfoCompat; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.components.emoji.EmojiEditText; import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.components.mention.MentionDeleter; import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate; import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher; +import org.thoughtcrime.securesms.conversation.MessageSendType; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -201,13 +201,13 @@ public class ComposeText extends EmojiEditText { return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } - public void setTransport(TransportOption transport) { + public void setMessageSendType(MessageSendType messageSendType) { final boolean useSystemEmoji = SignalStore.settings().isPreferSystemEmoji(); int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND; int inputType = getInputType(); - if (isLandscape()) setImeActionLabel(transport.getComposeHint(), EditorInfo.IME_ACTION_SEND); + if (isLandscape()) setImeActionLabel(getContext().getString(messageSendType.getComposeHintRes()), EditorInfo.IME_ACTION_SEND); else setImeActionLabel(null, 0); if (useSystemEmoji) { @@ -215,9 +215,9 @@ public class ComposeText extends EmojiEditText { } setImeOptions(imeOptions); - setHint(transport.getComposeHint(), - transport.getSimName().isPresent() - ? getContext().getString(R.string.conversation_activity__from_sim_name, transport.getSimName().get()) + setHint(getContext().getString(messageSendType.getComposeHintRes()), + messageSendType.getSimName() != null + ? getContext().getString(R.string.conversation_activity__from_sim_name, messageSendType.getSimName()) : null); setInputType(inputType); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SendButton.java b/app/src/main/java/org/thoughtcrime/securesms/components/SendButton.java deleted file mode 100644 index 6e325c9e7e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/SendButton.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatImageButton; - -import org.thoughtcrime.securesms.TransportOption; -import org.thoughtcrime.securesms.TransportOptions; -import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener; -import org.thoughtcrime.securesms.TransportOptionsPopup; -import org.thoughtcrime.securesms.util.ViewUtil; - -import java.util.Optional; - - -public class SendButton extends AppCompatImageButton - implements TransportOptions.OnTransportChangedListener, - TransportOptionsPopup.SelectedListener, - View.OnLongClickListener -{ - - private final TransportOptions transportOptions; - - private Optional transportOptionsPopup = Optional.empty(); - - @SuppressWarnings("unused") - public SendButton(Context context) { - super(context); - this.transportOptions = initializeTransportOptions(false); - ViewUtil.mirrorIfRtl(this, getContext()); - } - - @SuppressWarnings("unused") - public SendButton(Context context, AttributeSet attrs) { - super(context, attrs); - this.transportOptions = initializeTransportOptions(false); - ViewUtil.mirrorIfRtl(this, getContext()); - } - - @SuppressWarnings("unused") - public SendButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - this.transportOptions = initializeTransportOptions(false); - ViewUtil.mirrorIfRtl(this, getContext()); - } - - private TransportOptions initializeTransportOptions(boolean media) { - if (isInEditMode()) return null; - - TransportOptions transportOptions = new TransportOptions(getContext(), media); - transportOptions.addOnTransportChangedListener(this); - - setOnLongClickListener(this); - - return transportOptions; - } - - private TransportOptionsPopup getTransportOptionsPopup() { - if (!transportOptionsPopup.isPresent()) { - transportOptionsPopup = Optional.of(new TransportOptionsPopup(getContext(), this, this)); - } - return transportOptionsPopup.get(); - } - - public boolean isManualSelection() { - return transportOptions.isManualSelection(); - } - - public void addOnTransportChangedListener(OnTransportChangedListener listener) { - transportOptions.addOnTransportChangedListener(listener); - } - - public TransportOption getSelectedTransport() { - return transportOptions.getSelectedTransport(); - } - - public void resetAvailableTransports(boolean isMediaMessage) { - transportOptions.reset(isMediaMessage); - } - - public void disableTransport(TransportOption.Type type) { - transportOptions.disableTransport(type); - } - - public void setDefaultTransport(TransportOption.Type type) { - transportOptions.setDefaultTransport(type); - } - - public void setTransport(@NonNull TransportOption option) { - transportOptions.setSelectedTransport(option); - } - - public void setDefaultSubscriptionId(Optional subscriptionId) { - transportOptions.setDefaultSubscriptionId(subscriptionId); - } - - @Override - public void onSelected(TransportOption option) { - transportOptions.setSelectedTransport(option); - getTransportOptionsPopup().dismiss(); - } - - @Override - public void onChange(TransportOption newTransport, boolean isManualSelection) { - setImageResource(newTransport.getDrawable()); - setContentDescription(newTransport.getDescription()); - } - - @Override - public boolean onLongClick(View v) { - if (isEnabled() && transportOptions.getEnabledTransports().size() > 1) { - getTransportOptionsPopup().display(transportOptions.getEnabledTransports()); - return true; - } - - return false; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SendButton.kt b/app/src/main/java/org/thoughtcrime/securesms/components/SendButton.kt new file mode 100644 index 0000000000..bb54cfe6bd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SendButton.kt @@ -0,0 +1,165 @@ +package org.thoughtcrime.securesms.components + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.View.OnLongClickListener +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatImageButton +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.components.menu.ActionItem +import org.thoughtcrime.securesms.components.menu.SignalContextMenu +import org.thoughtcrime.securesms.conversation.MessageSendType +import org.thoughtcrime.securesms.util.ViewUtil +import java.lang.AssertionError +import java.util.concurrent.CopyOnWriteArrayList + +/** + * The send button you see in a conversation. + * Also encapsulates the long-press menu that allows users to switch [MessageSendType]s. + */ +class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImageButton(context, attributeSet), OnLongClickListener { + + companion object { + private val TAG = Log.tag(SendButton::class.java) + } + + private val listeners: MutableList = CopyOnWriteArrayList() + + private var availableSendTypes: List = MessageSendType.getAllAvailable(context, false) + private var activeMessageSendType: MessageSendType? = null + private var defaultTransportType: MessageSendType.TransportType = MessageSendType.TransportType.SMS + private var defaultSubscriptionId: Int? = null + private var popupContainer: ViewGroup? = null + + init { + setOnLongClickListener(this) + ViewUtil.mirrorIfRtl(this, getContext()) + } + + /** + * @return True if the [selectedSendType] was chosen manually by the user, otherwise false. + */ + val isManualSelection: Boolean + get() = activeMessageSendType != null + + /** + * The actively-selected send type. + */ + val selectedSendType: MessageSendType + get() { + activeMessageSendType?.let { + return it + } + + if (defaultTransportType === MessageSendType.TransportType.SMS) { + for (type in availableSendTypes) { + if (type.usesSmsTransport && (defaultSubscriptionId == null || type.simSubscriptionId == defaultSubscriptionId)) { + return type + } + } + } + + for (type in availableSendTypes) { + if (type.transportType === defaultTransportType) { + return type + } + } + + throw AssertionError("No options of default type!") + } + + fun addOnSelectionChangedListener(listener: SendTypeChangedListener) { + listeners.add(listener) + } + + fun triggerSelectedChangedEvent() { + onSelectionChanged(newType = selectedSendType, isManualSelection = false) + } + + fun resetAvailableTransports(isMediaMessage: Boolean) { + availableSendTypes = MessageSendType.getAllAvailable(context, isMediaMessage) + + if (!availableSendTypes.contains(activeMessageSendType)) { + Log.w(TAG, "[resetAvailableTransports] The active send type is no longer available. Unsetting.") + setSendType(null) + } else { + defaultTransportType = MessageSendType.TransportType.SMS + defaultSubscriptionId = null + onSelectionChanged(newType = selectedSendType, isManualSelection = false) + } + } + + fun disableTransportType(type: MessageSendType.TransportType) { + availableSendTypes = availableSendTypes.filterNot { it.transportType == type } + } + + fun setDefaultTransport(type: MessageSendType.TransportType) { + if (defaultTransportType == type) { + return + } + defaultTransportType = type + onSelectionChanged(newType = selectedSendType, isManualSelection = false) + } + + fun setSendType(sendType: MessageSendType?) { + if (activeMessageSendType == sendType) { + return + } + activeMessageSendType = sendType + onSelectionChanged(newType = selectedSendType, isManualSelection = true) + } + + fun setDefaultSubscriptionId(subscriptionId: Int?) { + if (defaultSubscriptionId == subscriptionId) { + return + } + defaultSubscriptionId = subscriptionId + onSelectionChanged(newType = selectedSendType, isManualSelection = false) + } + + /** + * Must be called with a view that is acceptable for determining the bounds of the popup selector. + */ + fun setPopupContainer(container: ViewGroup) { + popupContainer = container + } + + private fun onSelectionChanged(newType: MessageSendType, isManualSelection: Boolean) { + setImageResource(newType.buttonDrawableRes) + contentDescription = context.getString(newType.titleRes) + + for (listener in listeners) { + listener.onSendTypeChanged(newType, isManualSelection) + } + } + + override fun onLongClick(v: View): Boolean { + if (!isEnabled || availableSendTypes.size == 1) { + return false + } + + val currentlySelected: MessageSendType = selectedSendType + + val items = availableSendTypes + .filterNot { it == currentlySelected } + .map { option -> + ActionItem( + iconRes = option.menuDrawableRes, + title = option.getTitle(context), + action = { setSendType(option) } + ) + } + + SignalContextMenu.Builder((parent as View), popupContainer!!) + .preferredVerticalPosition(SignalContextMenu.VerticalPosition.ABOVE) + .offsetY(ViewUtil.dpToPx(8)) + .show(items) + + return true + } + + interface SendTypeChangedListener { + fun onSendTypeChanged(newType: MessageSendType, manuallySelected: Boolean) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt index eef27f773e..4186501260 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt @@ -25,6 +25,7 @@ class SignalContextMenu private constructor( val baseOffsetX: Int = 0, val baseOffsetY: Int = 0, val horizontalPosition: HorizontalPosition = HorizontalPosition.START, + val verticalPosition: VerticalPosition = VerticalPosition.BELOW, val onDismiss: Runnable? = null ) : PopupWindow( LayoutInflater.from(anchor.context).inflate(R.layout.signal_context_menu, null), @@ -41,6 +42,7 @@ class SignalContextMenu private constructor( init { setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.signal_context_menu_background)) + inputMethodMode = INPUT_METHOD_NOT_NEEDED isFocusable = true @@ -80,7 +82,10 @@ class SignalContextMenu private constructor( val offsetY: Int - if (menuBottomBound < screenBottomBound) { + if (verticalPosition == VerticalPosition.ABOVE && menuTopBound > screenTopBound) { + offsetY = -(anchorRect.height() + contentView.measuredHeight + baseOffsetY) + contextMenuList.setItems(items.reversed()) + } else if (menuBottomBound < screenBottomBound) { offsetY = baseOffsetY } else if (menuTopBound > screenTopBound) { offsetY = -(anchorRect.height() + contentView.measuredHeight + baseOffsetY) @@ -115,6 +120,10 @@ class SignalContextMenu private constructor( START, END } + enum class VerticalPosition { + ABOVE, BELOW + } + /** * @param anchor The view to put the pop-up on * @param container A parent of [anchor] that represents the acceptable boundaries of the popup @@ -128,6 +137,7 @@ class SignalContextMenu private constructor( var offsetX = 0 var offsetY = 0 var horizontalPosition = HorizontalPosition.START + var verticalPosition = VerticalPosition.BELOW fun onDismiss(onDismiss: Runnable): Builder { this.onDismiss = onDismiss @@ -149,6 +159,11 @@ class SignalContextMenu private constructor( return this } + fun preferredVerticalPosition(verticalPosition: VerticalPosition): Builder { + this.verticalPosition = verticalPosition + return this + } + fun show(items: List): SignalContextMenu { return SignalContextMenu( anchor = anchor, @@ -157,6 +172,7 @@ class SignalContextMenu private constructor( baseOffsetX = offsetX, baseOffsetY = offsetY, horizontalPosition = horizontalPosition, + verticalPosition = verticalPosition, onDismiss = onDismiss ).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 22656d2a69..bf46435b9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -115,7 +115,6 @@ import org.thoughtcrime.securesms.MuteDialog; import org.thoughtcrime.securesms.PromptMmsActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.ShortcutLauncherActivity; -import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.TombstoneAttachment; import org.thoughtcrime.securesms.audio.AudioRecorder; @@ -328,7 +327,6 @@ import java.util.concurrent.atomic.AtomicInteger; import kotlin.Unit; -import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; /** @@ -590,6 +588,8 @@ public class ConversationParentFragment extends Fragment if (isSearchRequested && savedInstanceState == null) { onCreateOptionsMenu(toolbar.getMenu(), requireActivity().getMenuInflater()); } + + sendButton.post(() -> sendButton.triggerSelectedChangedEvent()); } // TODO [alex] LargeScreenSupport -- This needs to be fed a stream of intents @@ -672,7 +672,7 @@ public class ConversationParentFragment extends Fragment EventBus.getDefault().register(this); initializeMmsEnabledCheck(); initializeIdentityRecords(); - composeText.setTransport(sendButton.getSelectedTransport()); + composeText.setMessageSendType(sendButton.getSelectedSendType()); Recipient recipientSnapshot = recipient.get(); @@ -731,7 +731,7 @@ public class ConversationParentFragment extends Fragment public void onConfigurationChanged(Configuration newConfig) { Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")"); super.onConfigurationChanged(newConfig); - composeText.setTransport(sendButton.getSelectedTransport()); + composeText.setMessageSendType(sendButton.getSelectedSendType()); if (emojiDrawerStub.resolved() && container.getCurrentInput() == emojiDrawerStub.get()) { container.hideAttachedInput(true); @@ -823,7 +823,7 @@ public class ConversationParentFragment extends Fragment return; } - sendButton.setTransport(result.getTransport()); + sendButton.setSendType(result.getMessageSendType()); if (result.isPushPreUpload()) { sendMediaMessage(result); @@ -831,7 +831,7 @@ public class ConversationParentFragment extends Fragment } long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds()); - int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1); + int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1); boolean initiating = threadId == -1; QuoteModel quote = result.isViewOnce() ? null : inputPanel.getQuote().orElse(null); SlideDeck slideDeck = new SlideDeck(); @@ -852,7 +852,7 @@ public class ConversationParentFragment extends Fragment final Context context = requireContext().getApplicationContext(); sendMediaMessage(result.getRecipientId(), - result.getTransport().isSms(), + result.getMessageSendType().usesSmsTransport(), result.getBody(), slideDeck, quote, @@ -1231,7 +1231,7 @@ public class ConversationParentFragment extends Fragment @Override public void onAttachmentMediaClicked(@NonNull Media media) { linkPreviewViewModel.onUserCancel(); - startActivityForResult(MediaSelectionActivity.editor(requireActivity(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); + startActivityForResult(MediaSelectionActivity.editor(requireActivity(), sendButton.getSelectedSendType(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); container.hideCurrentInput(composeText); } @@ -1239,7 +1239,7 @@ public class ConversationParentFragment extends Fragment public void onAttachmentSelectorClicked(@NonNull AttachmentKeyboardButton button) { switch (button) { case GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport(), inputPanel.getQuote().isPresent()); + AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedSendType(), inputPanel.getQuote().isPresent()); break; case FILE: AttachmentManager.selectDocument(this, PICK_DOCUMENT); @@ -1629,21 +1629,21 @@ public class ConversationParentFragment extends Fragment boolean smsEnabled = true; if (recipient.get().isPushGroup() || (!recipient.get().isMmsGroup() && !recipient.get().hasSmsAddress())) { - sendButton.disableTransport(Type.SMS); + sendButton.disableTransportType(MessageSendType.TransportType.SMS); smsEnabled = false; } if (!isSecureText && !isPushGroupConversation() && !recipient.get().isServiceIdOnly() && !recipient.get().isReleaseNotes() && smsEnabled) { - sendButton.disableTransport(Type.TEXTSECURE); + sendButton.disableTransportType(MessageSendType.TransportType.SIGNAL); } if (!recipient.get().isPushGroup() && recipient.get().isForceSmsSelection() && smsEnabled) { - sendButton.setDefaultTransport(Type.SMS); + sendButton.setDefaultTransport(MessageSendType.TransportType.SMS); } else { if (isSecureText || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) { - sendButton.setDefaultTransport(Type.TEXTSECURE); + sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL); } else { - sendButton.setDefaultTransport(Type.SMS); + sendButton.setDefaultTransport(MessageSendType.TransportType.SMS); } } @@ -1680,7 +1680,7 @@ public class ConversationParentFragment extends Fragment if (!Util.isEmpty(mediaList)) { Log.d(TAG, "Handling shared Media."); - Intent sendIntent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), mediaList, recipient.getId(), draftText); + Intent sendIntent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedSendType(), mediaList, recipient.getId(), draftText); startActivityForResult(sendIntent, MEDIA_SENDER); return new SettableFuture<>(false); } @@ -2029,7 +2029,7 @@ public class ConversationParentFragment extends Fragment private void updateDefaultSubscriptionId(Optional defaultSubscriptionId) { Log.i(TAG, "updateDefaultSubscriptionId(" + defaultSubscriptionId.orElse(null) + ")"); - sendButton.setDefaultSubscriptionId(defaultSubscriptionId); + sendButton.setDefaultSubscriptionId(defaultSubscriptionId.orElse(null)); } private void initializeMmsEnabledCheck() { @@ -2150,6 +2150,8 @@ public class ConversationParentFragment extends Fragment releaseChannelUnmute = ViewUtil.findStubById(view, R.id.conversation_release_notes_unmute_stub); joinGroupCallButton = view.findViewById(R.id.conversation_group_call_join); + sendButton.setPopupContainer((ViewGroup) view); + container.setIsBubble(isInBubble()); container.addOnKeyboardShownListener(this); inputPanel.setListener(this); @@ -2168,16 +2170,16 @@ public class ConversationParentFragment extends Fragment attachButton.setOnLongClickListener(new AttachButtonLongClickListener()); sendButton.setOnClickListener(sendButtonListener); sendButton.setEnabled(true); - sendButton.addOnTransportChangedListener((newTransport, manuallySelected) -> { + sendButton.addOnSelectionChangedListener((newMessageSendType, manuallySelected) -> { calculateCharactersRemaining(); updateLinkPreviewState(); - linkPreviewViewModel.onTransportChanged(newTransport.isSms()); - composeText.setTransport(newTransport); + linkPreviewViewModel.onTransportChanged(newMessageSendType.usesSmsTransport()); + composeText.setMessageSendType(newMessageSendType); - buttonToggle.getBackground().setColorFilter(getButtonToggleBackgroundColor(newTransport), PorterDuff.Mode.MULTIPLY); + buttonToggle.getBackground().setColorFilter(getButtonToggleBackgroundColor(newMessageSendType), PorterDuff.Mode.MULTIPLY); buttonToggle.getBackground().invalidateSelf(); - if (manuallySelected) recordTransportPreference(newTransport); + if (manuallySelected) recordTransportPreference(newMessageSendType); }); titleView.setOnStoryRingClickListener(v -> handleStoryRingClick()); @@ -2222,13 +2224,13 @@ public class ConversationParentFragment extends Fragment material3OnScrollHelper = new Material3OnScrollHelper(Collections.singletonList(toolbarBackground), Collections.emptyList(), this::updateStatusBarColor); } - private @ColorInt int getButtonToggleBackgroundColor(TransportOption newTransport) { - if (newTransport.isSms()) { - return newTransport.getBackgroundColor(); + private @ColorInt int getButtonToggleBackgroundColor(MessageSendType newTransport) { + if (newTransport.usesSmsTransport()) { + return getResources().getColor(newTransport.getBackgroundColorRes()); } else if (recipient != null) { return getRecipient().getChatColors().asSingleColor(); } else { - return newTransport.getBackgroundColor(); + return getResources().getColor(newTransport.getBackgroundColorRes()); } } @@ -2728,7 +2730,7 @@ public class ConversationParentFragment extends Fragment } Media media = new Media(uri, mimeType, 0, width, height, 0, 0, borderless, videoGif, Optional.empty(), Optional.empty(), Optional.empty()); - startActivityForResult(MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); + startActivityForResult(MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedSendType(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER); return new SettableFuture<>(false); } else { return attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height); @@ -2749,7 +2751,7 @@ public class ConversationParentFragment extends Fragment } private void sendSharedContact(List contacts) { - int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1); + int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1); long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds()); boolean initiating = threadId == -1; @@ -2909,8 +2911,8 @@ public class ConversationParentFragment extends Fragment private void calculateCharactersRemaining() { String messageBody = composeText.getTextTrimmed().toString(); - TransportOption transportOption = sendButton.getSelectedTransport(); - CharacterState characterState = transportOption.calculateCharacters(messageBody); + MessageSendType sendType = sendButton.getSelectedSendType(); + CharacterState characterState = sendType.calculateCharacters(messageBody); if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) { charactersLeft.setText(String.format(Locale.getDefault(), @@ -2968,7 +2970,7 @@ public class ConversationParentFragment extends Fragment } private boolean isSmsForced() { - return sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); + return sendButton.isManualSelection() && sendButton.getSelectedSendType().usesSmsTransport(); } protected Recipient getRecipient() { @@ -2989,9 +2991,9 @@ public class ConversationParentFragment extends Fragment } private MediaConstraints getCurrentMediaConstraints() { - return sendButton.getSelectedTransport().getType() == Type.TEXTSECURE + return sendButton.getSelectedSendType().usesSignalTransport() ? MediaConstraints.getPushMediaConstraints() - : MediaConstraints.getMmsMediaConstraints(sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1)); + : MediaConstraints.getMmsMediaConstraints(sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1)); } private void markLastSeen() { @@ -3050,12 +3052,12 @@ public class ConversationParentFragment extends Fragment } String message = getMessage(); - TransportOption transport = sendButton.getSelectedTransport(); - boolean forceSms = (recipient.isForceSmsSelection() || sendButton.isManualSelection()) && transport.isSms(); - int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1); + MessageSendType sendType = sendButton.getSelectedSendType(); + boolean forceSms = (recipient.isForceSmsSelection() || sendButton.isManualSelection()) && sendType.usesSmsTransport(); + int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1); long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds()); boolean initiating = threadId == -1; - boolean needsSplit = !transport.isSms() && message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize; + boolean needsSplit = !sendType.usesSmsTransport() && message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize; boolean isMediaMessage = attachmentManager.isAttachmentPresent() || recipient.isGroup() || recipient.getEmail().isPresent() || @@ -3160,7 +3162,7 @@ public class ConversationParentFragment extends Fragment final long thread = this.threadId; if (sendPush) { - MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(requireContext(), body, sendButton.getSelectedTransport().calculateCharacters(body).maxPrimaryMessageSize); + MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(requireContext(), body, sendButton.getSelectedSendType().calculateCharacters(body).maxPrimaryMessageSize); body = splitMessage.getBody(); if (splitMessage.getTextSlide().isPresent()) { @@ -3294,7 +3296,7 @@ public class ConversationParentFragment extends Fragment } private void updateLinkPreviewState() { - if (SignalStore.settings().isLinkPreviewsEnabled() && isSecureText && !sendButton.getSelectedTransport().isSms() && !attachmentManager.isAttachmentPresent() && getContext() != null) { + if (SignalStore.settings().isLinkPreviewsEnabled() && isSecureText && !sendButton.getSelectedSendType().usesSmsTransport() && !attachmentManager.isAttachmentPresent() && getContext() != null) { linkPreviewViewModel.onEnabled(); linkPreviewViewModel.onTextChanged(requireContext(), composeText.getTextTrimmed().toString(), composeText.getSelectionStart(), composeText.getSelectionEnd()); } else { @@ -3302,16 +3304,16 @@ public class ConversationParentFragment extends Fragment } } - private void recordTransportPreference(TransportOption transportOption) { + private void recordTransportPreference(MessageSendType sendType) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { RecipientDatabase recipientDatabase = SignalDatabase.recipients(); - recipientDatabase.setDefaultSubscriptionId(recipient.getId(), transportOption.getSimSubscriptionId().orElse(-1)); + recipientDatabase.setDefaultSubscriptionId(recipient.getId(), sendType.getSimSubscriptionIdOr(-1)); if (!recipient.resolve().isPushGroup()) { - recipientDatabase.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && transportOption.isSms()); + recipientDatabase.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && sendType.usesSmsTransport()); } return null; @@ -3446,9 +3448,9 @@ public class ConversationParentFragment extends Fragment } private void sendVoiceNote(@NonNull Uri uri, long size) { - boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); + boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedSendType().usesSmsTransport(); boolean initiating = threadId == -1; - int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1); + int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1); long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds()); AudioSlide audioSlide = new AudioSlide(requireContext(), uri, size, MediaUtil.AUDIO_AAC, true); SlideDeck slideDeck = new SlideDeck(); @@ -3487,23 +3489,23 @@ public class ConversationParentFragment extends Fragment } private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull String contentType, @NonNull Uri uri, long size, boolean clearCompose) { - if (sendButton.getSelectedTransport().isSms()) { + if (sendButton.getSelectedSendType().usesSmsTransport()) { Media media = new Media(uri, contentType, System.currentTimeMillis(), StickerSlide.WIDTH, StickerSlide.HEIGHT, size, 0, false, false, Optional.empty(), Optional.empty(), Optional.empty()); - Intent intent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()); + Intent intent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedSendType(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()); startActivityForResult(intent, MEDIA_SENDER); return; } long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds()); - int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1); + int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1); boolean initiating = threadId == -1; - TransportOption transport = sendButton.getSelectedTransport(); + MessageSendType sendType = sendButton.getSelectedSendType(); SlideDeck slideDeck = new SlideDeck(); Slide stickerSlide = new StickerSlide(requireContext(), uri, size, stickerLocator, contentType); slideDeck.addSlide(stickerSlide); - sendMediaMessage(recipient.getId(), transport.isSms(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), expiresIn, false, subscriptionId, initiating, clearCompose, null); + sendMediaMessage(recipient.getId(), sendType.usesSmsTransport(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), expiresIn, false, subscriptionId, initiating, clearCompose, null); } private void silentlySetComposeText(String text) { @@ -3558,7 +3560,7 @@ public class ConversationParentFragment extends Fragment @Override public void openGifSearch() { - AttachmentManager.selectGif(this, ConversationParentFragment.PICK_GIF, recipient.getId(), sendButton.getSelectedTransport(), isMms(), composeText.getTextTrimmed()); + AttachmentManager.selectGif(this, ConversationParentFragment.PICK_GIF, recipient.getId(), sendButton.getSelectedSendType(), isMms(), composeText.getTextTrimmed()); } @Override @@ -3651,7 +3653,7 @@ public class ConversationParentFragment extends Fragment .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) .onAllGranted(() -> { composeText.clearFocus(); - startActivityForResult(MediaSelectionActivity.camera(requireActivity(), sendButton.getSelectedTransport(), recipient.getId(), inputPanel.getQuote().isPresent()), MEDIA_SENDER); + startActivityForResult(MediaSelectionActivity.camera(requireActivity(), sendButton.getSelectedSendType(), recipient.getId(), inputPanel.getQuote().isPresent()), MEDIA_SENDER); requireActivity().overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); }) .onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) @@ -4146,7 +4148,7 @@ public class ConversationParentFragment extends Fragment } long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds()); - int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1); + int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1); boolean initiating = threadId == -1; SlideDeck slideDeck = new SlideDeck(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/MessageSendType.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/MessageSendType.kt new file mode 100644 index 0000000000..91a033b40b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/MessageSendType.kt @@ -0,0 +1,164 @@ +package org.thoughtcrime.securesms.conversation + +import android.Manifest +import android.content.Context +import android.os.Parcelable +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import kotlinx.parcelize.Parcelize +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.permissions.Permissions +import org.thoughtcrime.securesms.util.CharacterCalculator +import org.thoughtcrime.securesms.util.MmsCharacterCalculator +import org.thoughtcrime.securesms.util.PushCharacterCalculator +import org.thoughtcrime.securesms.util.SmsCharacterCalculator +import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat +import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat +import java.lang.IllegalArgumentException + +/** + * The kinds of messages you can send, e.g. a plain Signal message, an SMS message, etc. + */ +@Parcelize +sealed class MessageSendType( + @StringRes + val titleRes: Int, + @StringRes + val composeHintRes: Int, + @DrawableRes + val buttonDrawableRes: Int, + @DrawableRes + val menuDrawableRes: Int, + @ColorRes + val backgroundColorRes: Int, + val transportType: TransportType, + val characterCalculator: CharacterCalculator, + open val simName: CharSequence? = null, + open val simSubscriptionId: Int? = null +) : Parcelable { + + @get:JvmName("usesSmsTransport") + val usesSmsTransport + get() = transportType == TransportType.SMS + + @get:JvmName("usesSignalTransport") + val usesSignalTransport + get() = transportType == TransportType.SIGNAL + + fun calculateCharacters(body: String): CharacterCalculator.CharacterState { + return characterCalculator.calculateCharacters(body) + } + + fun getSimSubscriptionIdOr(fallback: Int): Int { + return simSubscriptionId ?: fallback + } + + open fun getTitle(context: Context): String { + return context.getString(titleRes) + } + + /** + * A type representing an SMS message, with optional SIM fields for multi-SIM devices. + */ + @Parcelize + data class SmsMessageSendType(override val simName: CharSequence? = null, override val simSubscriptionId: Int? = null) : MessageSendType( + titleRes = R.string.ConversationActivity_transport_insecure_sms, + composeHintRes = R.string.conversation_activity__type_message_sms_insecure, + buttonDrawableRes = R.drawable.ic_send_unlock_24, + menuDrawableRes = R.drawable.ic_insecure_24, + backgroundColorRes = R.color.core_grey_50, + transportType = TransportType.SMS, + characterCalculator = SmsCharacterCalculator(), + simName = simName, + simSubscriptionId = simSubscriptionId + ) { + override fun getTitle(context: Context): String { + return if (simName == null) { + super.getTitle(context) + } else { + context.getString(R.string.ConversationActivity_transport_insecure_sms_with_sim, simName) + } + } + } + + /** + * A type representing an MMS message, with optional SIM fields for multi-SIM devices. + */ + @Parcelize + data class MmsMessageSendType(override val simName: CharSequence? = null, override val simSubscriptionId: Int? = null) : MessageSendType( + titleRes = R.string.ConversationActivity_transport_insecure_mms, + composeHintRes = R.string.conversation_activity__type_message_mms_insecure, + buttonDrawableRes = R.drawable.ic_send_unlock_24, + menuDrawableRes = R.drawable.ic_insecure_24, + backgroundColorRes = R.color.core_grey_50, + transportType = TransportType.SMS, + characterCalculator = MmsCharacterCalculator(), + simName = simName, + simSubscriptionId = simSubscriptionId + ) { + override fun getTitle(context: Context): String { + return if (simName == null) { + super.getTitle(context) + } else { + context.getString(R.string.ConversationActivity_transport_insecure_sms_with_sim, simName) + } + } + } + + /** + * A type representing a basic Signal message. + */ + @Parcelize + object SignalMessageSendType : MessageSendType( + titleRes = R.string.ConversationActivity_transport_signal, + composeHintRes = R.string.conversation_activity__type_message_push, + buttonDrawableRes = R.drawable.ic_send_lock_24, + menuDrawableRes = R.drawable.ic_secure_24, + backgroundColorRes = R.color.core_ultramarine, + transportType = TransportType.SIGNAL, + characterCalculator = PushCharacterCalculator() + ) + + enum class TransportType { + SIGNAL, SMS + } + + companion object { + /** + * Returns a list of all available [MessageSendType]s. Requires [Manifest.permission.READ_PHONE_STATE] in order to get available + * SMS options. + */ + @JvmStatic + fun getAllAvailable(context: Context, isMedia: Boolean = false): List { + val options: MutableList = mutableListOf() + + options += SignalMessageSendType + + if (!Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE)) { + return options + } + + val subscriptions: Collection = SubscriptionManagerCompat(context).activeAndReadySubscriptionInfos + + if (subscriptions.size < 2) { + options += if (isMedia) MmsMessageSendType() else SmsMessageSendType() + } else { + options += subscriptions.map { + if (isMedia) { + MmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId) + } else { + SmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId) + } + } + } + + return options + } + + @JvmStatic + fun getFirstForTransport(context: Context, isMedia: Boolean, transportType: TransportType): MessageSendType { + return getAllAvailable(context, isMedia).firstOrNull { it.transportType == transportType } ?: throw IllegalArgumentException("No options available for desired type $transportType!") + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/Multiselect.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/Multiselect.kt index cfe59cfc96..7dc8a66e13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/Multiselect.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/Multiselect.kt @@ -5,10 +5,9 @@ import android.content.Context import android.content.pm.PackageManager import android.net.Uri import androidx.core.content.ContextCompat -import org.thoughtcrime.securesms.TransportOption -import org.thoughtcrime.securesms.TransportOptions import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.conversation.ConversationMessage +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.MediaConstraints @@ -84,10 +83,9 @@ object Multiselect { return false } - val options = TransportOptions(context, true) - options.setDefaultTransport(TransportOption.Type.SMS) + val sendType: MessageSendType = MessageSendType.getFirstForTransport(context, true, MessageSendType.TransportType.SMS) - val mmsConstraints = MediaConstraints.getMmsMediaConstraints(options.selectedTransport.simSubscriptionId.orElse(-1)) + val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1) return mmsConstraints.isSatisfied(context, mediaUri, mediaType, mediaSize) || mmsConstraints.canResize(mediaType) } @@ -108,10 +106,9 @@ object Multiselect { return false } - val options = TransportOptions(context, true) - options.setDefaultTransport(TransportOption.Type.SMS) + val sendType: MessageSendType = MessageSendType.getFirstForTransport(context, true, MessageSendType.TransportType.SMS) - val mmsConstraints = MediaConstraints.getMmsMediaConstraints(options.selectedTransport.simSubscriptionId.orElse(-1)) + val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1) return mmsConstraints.isSatisfied(context, attachment) || mmsConstraints.canResize(attachment) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java index dc79cd386d..f7ba42ad92 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java @@ -13,7 +13,7 @@ import androidx.lifecycle.ViewModelProviders; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.TransportOption; +import org.thoughtcrime.securesms.conversation.MessageSendType; import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Fragment; import org.thoughtcrime.securesms.giph.mp4.GiphyMp4SaveResult; import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ViewModel; @@ -47,7 +47,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar private GiphyMp4ViewModel giphyMp4ViewModel; private AlertDialog progressDialog; private RecipientId recipientId; - private TransportOption transport; + private MessageSendType sendType; private CharSequence text; @Override @@ -62,7 +62,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar final boolean forMms = getIntent().getBooleanExtra(EXTRA_IS_MMS, false); recipientId = getIntent().getParcelableExtra(EXTRA_RECIPIENT_ID); - transport = getIntent().getParcelableExtra(EXTRA_TRANSPORT); + sendType = getIntent().getParcelableExtra(EXTRA_TRANSPORT); text = getIntent().getCharSequenceExtra(EXTRA_TEXT); giphyMp4ViewModel = ViewModelProviders.of(this, new GiphyMp4ViewModel.Factory(forMms)).get(GiphyMp4ViewModel.class); @@ -121,7 +121,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar } Media media = new Media(success.getBlobUri(), mimeType, 0, success.getWidth(), success.getHeight(), 0, 0, false, true, Optional.empty(), Optional.empty(), Optional.empty()); - startActivityForResult(MediaSelectionActivity.editor(this, transport, Collections.singletonList(media), recipientId, text), MEDIA_SENDER); + startActivityForResult(MediaSelectionActivity.editor(this, sendType, Collections.singletonList(media), recipientId, text), MEDIA_SENDER); } private void handleGiphyMp4ErrorResult(@NonNull GiphyMp4SaveResult.Error error) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.java index f2d8379575..62dee53b75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.java @@ -6,8 +6,8 @@ import android.os.Parcelable; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.conversation.ConversationActivity; +import org.thoughtcrime.securesms.conversation.MessageSendType; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.StoryType; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -30,7 +30,7 @@ public class MediaSendActivityResult implements Parcelable { private final Collection uploadResults; private final Collection nonUploadedMedia; private final String body; - private final TransportOption transport; + private final MessageSendType sendType; private final boolean viewOnce; private final Collection mentions; private final StoryType storyType; @@ -47,32 +47,32 @@ public class MediaSendActivityResult implements Parcelable { public static @NonNull MediaSendActivityResult forPreUpload(@NonNull RecipientId recipientId, @NonNull Collection uploadResults, @NonNull String body, - @NonNull TransportOption transport, + @NonNull MessageSendType sendType, boolean viewOnce, @NonNull List mentions, @NonNull StoryType storyType) { Preconditions.checkArgument(uploadResults.size() > 0, "Must supply uploadResults!"); - return new MediaSendActivityResult(recipientId, uploadResults, Collections.emptyList(), body, transport, viewOnce, mentions, storyType); + return new MediaSendActivityResult(recipientId, uploadResults, Collections.emptyList(), body, sendType, viewOnce, mentions, storyType); } public static @NonNull MediaSendActivityResult forTraditionalSend(@NonNull RecipientId recipientId, @NonNull List nonUploadedMedia, @NonNull String body, - @NonNull TransportOption transport, + @NonNull MessageSendType sendType, boolean viewOnce, @NonNull List mentions, @NonNull StoryType storyType) { Preconditions.checkArgument(nonUploadedMedia.size() > 0, "Must supply media!"); - return new MediaSendActivityResult(recipientId, Collections.emptyList(), nonUploadedMedia, body, transport, viewOnce, mentions, storyType); + return new MediaSendActivityResult(recipientId, Collections.emptyList(), nonUploadedMedia, body, sendType, viewOnce, mentions, storyType); } private MediaSendActivityResult(@NonNull RecipientId recipientId, @NonNull Collection uploadResults, @NonNull List nonUploadedMedia, @NonNull String body, - @NonNull TransportOption transport, + @NonNull MessageSendType sendType, boolean viewOnce, @NonNull List mentions, @NonNull StoryType storyType) @@ -81,7 +81,7 @@ public class MediaSendActivityResult implements Parcelable { this.uploadResults = uploadResults; this.nonUploadedMedia = nonUploadedMedia; this.body = body; - this.transport = transport; + this.sendType = sendType; this.viewOnce = viewOnce; this.mentions = mentions; this.storyType = storyType; @@ -92,7 +92,7 @@ public class MediaSendActivityResult implements Parcelable { this.uploadResults = ParcelUtil.readParcelableCollection(in, PreUploadResult.class); this.nonUploadedMedia = ParcelUtil.readParcelableCollection(in, Media.class); this.body = in.readString(); - this.transport = in.readParcelable(TransportOption.class.getClassLoader()); + this.sendType = in.readParcelable(MessageSendType.class.getClassLoader()); this.viewOnce = ParcelUtil.readBoolean(in); this.mentions = ParcelUtil.readParcelableCollection(in, Mention.class); this.storyType = StoryType.fromCode(in.readInt()); @@ -118,8 +118,8 @@ public class MediaSendActivityResult implements Parcelable { return body; } - public @NonNull TransportOption getTransport() { - return transport; + public @NonNull MessageSendType getMessageSendType() { + return sendType; } public boolean isViewOnce() { @@ -157,7 +157,7 @@ public class MediaSendActivityResult implements Parcelable { ParcelUtil.writeParcelableCollection(dest, uploadResults); ParcelUtil.writeParcelableCollection(dest, nonUploadedMedia); dest.writeString(body); - dest.writeParcelable(transport, 0); + dest.writeParcelable(sendType, 0); ParcelUtil.writeBoolean(dest, viewOnce); ParcelUtil.writeParcelableCollection(dest, mentions); dest.writeInt(storyType.getCode()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index a422a3602d..6fa5358647 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -23,12 +23,11 @@ import org.signal.core.util.BreakIteratorCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.TransportOption -import org.thoughtcrime.securesms.TransportOptions import org.thoughtcrime.securesms.components.emoji.EmojiEventListener import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.contacts.paged.ContactSearchState +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFullScreenDialogFragment import org.thoughtcrime.securesms.conversation.mutiselect.forward.SearchConfigurationProvider import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog @@ -87,12 +86,12 @@ class MediaSelectionActivity : override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { setContentView(R.layout.media_selection_activity) - val transportOption: TransportOption = requireNotNull(intent.getParcelableExtra(TRANSPORT_OPTION)) + val sendType: MessageSendType = requireNotNull(intent.getParcelableExtra(MESSAGE_SEND_TYPE)) val initialMedia: List = intent.getParcelableArrayListExtra(MEDIA) ?: listOf() val message: CharSequence? = if (shareToTextStory) null else draftText val isReply: Boolean = intent.getBooleanExtra(IS_REPLY, false) - val factory = MediaSelectionViewModel.Factory(destination, transportOption, initialMedia, message, isReply, isStory, MediaSelectionRepository(this)) + val factory = MediaSelectionViewModel.Factory(destination, sendType, initialMedia, message, isReply, isStory, MediaSelectionRepository(this)) viewModel = ViewModelProvider(this, factory)[MediaSelectionViewModel::class.java] val textStoryToggle: ConstraintLayout = findViewById(R.id.switch_widget) @@ -346,7 +345,7 @@ class MediaSelectionActivity : private const val NAV_HOST_TAG = "NAV_HOST" private const val START_ACTION = "start.action" - private const val TRANSPORT_OPTION = "transport.option" + private const val MESSAGE_SEND_TYPE = "message.send.type" private const val MEDIA = "media" private const val MESSAGE = "message" private const val DESTINATION = "destination" @@ -371,14 +370,14 @@ class MediaSelectionActivity : @JvmStatic fun camera( context: Context, - transportOption: TransportOption, + messageSendType: MessageSendType, recipientId: RecipientId, isReply: Boolean ): Intent { return buildIntent( context = context, startAction = R.id.action_directly_to_mediaCaptureFragment, - transportOption = transportOption, + messageSendType = messageSendType, destination = MediaSelectionDestination.SingleRecipient(recipientId), isReply = isReply ) @@ -387,7 +386,7 @@ class MediaSelectionActivity : @JvmStatic fun gallery( context: Context, - transportOption: TransportOption, + messageSendType: MessageSendType, media: List, recipientId: RecipientId, message: CharSequence?, @@ -396,7 +395,7 @@ class MediaSelectionActivity : return buildIntent( context = context, startAction = R.id.action_directly_to_mediaGalleryFragment, - transportOption = transportOption, + messageSendType = messageSendType, media = media, destination = MediaSelectionDestination.SingleRecipient(recipientId), message = message, @@ -407,14 +406,14 @@ class MediaSelectionActivity : @JvmStatic fun editor( context: Context, - transportOption: TransportOption, + messageSendType: MessageSendType, media: List, recipientId: RecipientId, message: CharSequence? ): Intent { return buildIntent( context = context, - transportOption = transportOption, + messageSendType = messageSendType, media = media, destination = MediaSelectionDestination.SingleRecipient(recipientId), message = message @@ -424,7 +423,7 @@ class MediaSelectionActivity : @JvmStatic fun share( context: Context, - transportOption: TransportOption, + messageSendType: MessageSendType, media: List, recipientSearchKeys: List, message: CharSequence?, @@ -432,7 +431,7 @@ class MediaSelectionActivity : ): Intent { return buildIntent( context = context, - transportOption = transportOption, + messageSendType = messageSendType, media = media, destination = MediaSelectionDestination.MultipleRecipients(recipientSearchKeys), message = message, @@ -444,7 +443,7 @@ class MediaSelectionActivity : private fun buildIntent( context: Context, startAction: Int = -1, - transportOption: TransportOption = TransportOptions.getPushTransportOption(context), + messageSendType: MessageSendType = MessageSendType.SignalMessageSendType, media: List = listOf(), destination: MediaSelectionDestination = MediaSelectionDestination.ChooseAfterMediaSelection, message: CharSequence? = null, @@ -454,7 +453,7 @@ class MediaSelectionActivity : ): Intent { return Intent(context, MediaSelectionActivity::class.java).apply { putExtra(START_ACTION, startAction) - putExtra(TRANSPORT_OPTION, transportOption) + putExtra(MESSAGE_SEND_TYPE, messageSendType) putParcelableArrayListExtra(MEDIA, ArrayList(media)) putExtra(MESSAGE, message) putExtra(DESTINATION, destination.toBundle()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt index 79ae0d7f36..de54957170 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt @@ -11,8 +11,8 @@ import org.signal.core.util.BreakIteratorCompat import org.signal.core.util.ThreadUtil import org.signal.core.util.logging.Log import org.signal.imageeditor.core.model.EditorModel -import org.thoughtcrime.securesms.TransportOption import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.ThreadDatabase @@ -76,7 +76,7 @@ class MediaSelectionRepository(context: Context) { singleContact: ContactSearchKey.RecipientSearchKey?, contacts: List, mentions: List, - transport: TransportOption + sendType: MessageSendType ): Maybe { if (isSms && contacts.isNotEmpty()) { throw IllegalStateException("Provided recipients to send to, but this is SMS!") @@ -106,9 +106,9 @@ class MediaSelectionRepository(context: Context) { if (isSms || MessageSender.isLocalSelfSend(context, singleRecipient, isSms)) { Log.i(TAG, "SMS or local self-send. Skipping pre-upload.") - emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, StoryType.NONE)) + emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, sendType, isViewOnce, trimmedMentions, StoryType.NONE)) } else { - val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, transport.calculateCharacters(trimmedBody).maxPrimaryMessageSize) + val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, sendType.calculateCharacters(trimmedBody).maxPrimaryMessageSize) val splitBody = splitMessage.body if (splitMessage.textSlide.isPresent) { @@ -135,10 +135,10 @@ class MediaSelectionRepository(context: Context) { uploadRepository.deleteAbandonedAttachments() emitter.onComplete() } else if (uploadResults.isNotEmpty()) { - emitter.onSuccess(MediaSendActivityResult.forPreUpload(singleRecipient!!.id, uploadResults, splitBody, transport, isViewOnce, trimmedMentions, storyType)) + emitter.onSuccess(MediaSendActivityResult.forPreUpload(singleRecipient!!.id, uploadResults, splitBody, sendType, isViewOnce, trimmedMentions, storyType)) } else { Log.w(TAG, "Got empty upload results! isSms: $isSms, updatedMedia.size(): ${updatedMedia.size}, isViewOnce: $isViewOnce, target: $singleContact") - emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, storyType)) + emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, sendType, isViewOnce, trimmedMentions, storyType)) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt index 73d421036b..2ca383d71f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt @@ -1,7 +1,7 @@ package org.thoughtcrime.securesms.mediasend.v2 import android.net.Uri -import org.thoughtcrime.securesms.TransportOption +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendConstants @@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.recipients.Recipient data class MediaSelectionState( - val transportOption: TransportOption, + val sendType: MessageSendType, val selectedMedia: List = listOf(), val focusedMedia: Media? = null, val recipient: Recipient? = null, @@ -25,7 +25,7 @@ data class MediaSelectionState( val isStory: Boolean ) { - val maxSelection = if (transportOption.isSms) { + val maxSelection = if (sendType.usesSmsTransport) { MediaSendConstants.MAX_SMS } else { MediaSendConstants.MAX_PUSH diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt index e14db015ec..ab0015c3ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt @@ -10,9 +10,9 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.subjects.PublishSubject -import org.thoughtcrime.securesms.TransportOption import org.thoughtcrime.securesms.components.mention.MentionAnnotation import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult import org.thoughtcrime.securesms.mediasend.VideoEditorFragment @@ -31,7 +31,7 @@ import java.util.Collections */ class MediaSelectionViewModel( val destination: MediaSelectionDestination, - transportOption: TransportOption, + sendType: MessageSendType, initialMedia: List, initialMessage: CharSequence?, val isReply: Boolean, @@ -41,7 +41,7 @@ class MediaSelectionViewModel( private val store: Store = Store( MediaSelectionState( - transportOption = transportOption, + sendType = sendType, message = initialMessage, isStory = isStory ) @@ -62,7 +62,7 @@ class MediaSelectionViewModel( store.update { it.copy( isMeteredConnection = metered, - isPreUploadEnabled = shouldPreUpload(metered, it.transportOption.isSms, it.recipient) + isPreUploadEnabled = shouldPreUpload(metered, it.sendType.usesSmsTransport, it.recipient) ) } } @@ -75,7 +75,7 @@ class MediaSelectionViewModel( store.update(Recipient.live(recipientSearchKey.recipientId).liveData) { r, s -> s.copy( recipient = r, - isPreUploadEnabled = shouldPreUpload(s.isMeteredConnection, s.transportOption.isSms, r) + isPreUploadEnabled = shouldPreUpload(s.isMeteredConnection, s.sendType.usesSmsTransport, r) ) } } @@ -246,8 +246,8 @@ class MediaSelectionViewModel( } fun getMediaConstraints(): MediaConstraints { - return if (store.state.transportOption.isSms) { - MediaConstraints.getMmsMediaConstraints(store.state.transportOption.simSubscriptionId.orElse(-1)) + return if (store.state.sendType.usesSmsTransport) { + MediaConstraints.getMmsMediaConstraints(store.state.sendType.simSubscriptionId ?: -1) } else { MediaConstraints.getPushMediaConstraints() } @@ -293,18 +293,18 @@ class MediaSelectionViewModel( store.state.editorStateMap, store.state.quality, store.state.message, - store.state.transportOption.isSms, + store.state.sendType.usesSmsTransport, isViewOnceEnabled(), destination.getRecipientSearchKey(), selectedContacts.ifEmpty { destination.getRecipientSearchKeyList() }, MentionAnnotation.getMentionsFromAnnotations(store.state.message), - store.state.transportOption + store.state.sendType ) ) } private fun isViewOnceEnabled(): Boolean { - return !store.state.transportOption.isSms && + return !store.state.sendType.usesSmsTransport && store.state.selectedMedia.size == 1 && store.state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE } @@ -427,7 +427,7 @@ class MediaSelectionViewModel( class Factory( private val destination: MediaSelectionDestination, - private val transportOption: TransportOption, + private val sendType: MessageSendType, private val initialMedia: List, private val initialMessage: CharSequence?, private val isReply: Boolean, @@ -435,7 +435,7 @@ class MediaSelectionViewModel( private val repository: MediaSelectionRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return requireNotNull(modelClass.cast(MediaSelectionViewModel(destination, transportOption, initialMedia, initialMessage, isReply, isStory, repository))) + return requireNotNull(modelClass.cast(MediaSelectionViewModel(destination, sendType, initialMedia, initialMessage, isReply, isStory, repository))) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index aae728ea0c..32a3e7506f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -26,8 +26,8 @@ import androidx.viewpager2.widget.ViewPager2 import app.cash.exhaustive.Exhaustive import io.reactivex.rxjava3.disposables.CompositeDisposable import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.TransportOption import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult @@ -200,7 +200,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) { state.selectedMedia.map { MediaReviewSelectedItem.Model(it, state.focusedMedia == it) } + MediaReviewAddItem.Model ) - presentSendButton(state.transportOption) + presentSendButton(state.sendType) presentPager(state) presentAddMessageEntry(state.message) presentImageQualityToggle(state.quality) @@ -289,8 +289,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) { ) } - private fun presentSendButton(transportOption: TransportOption) { - val sendButtonTint = if (transportOption.type == TransportOption.Type.TEXTSECURE) { + private fun presentSendButton(sendType: MessageSendType) { + val sendButtonTint = if (sendType.usesSignalTransport) { R.color.core_ultramarine } else { R.color.core_grey_50 diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index 69433617d4..de29f1721d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -42,7 +42,6 @@ import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.DocumentView; @@ -50,6 +49,7 @@ import org.thoughtcrime.securesms.components.RemovableEditableMediaView; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.location.SignalMapView; import org.thoughtcrime.securesms.components.location.SignalPlace; +import org.thoughtcrime.securesms.conversation.MessageSendType; import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.maps.PlacePickerActivity; import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity; @@ -374,12 +374,12 @@ public class AttachmentManager { selectMediaType(fragment, "*/*", null, requestCode); } - public static void selectGallery(Fragment fragment, int requestCode, @NonNull Recipient recipient, @NonNull CharSequence body, @NonNull TransportOption transport, boolean hasQuote) { + public static void selectGallery(Fragment fragment, int requestCode, @NonNull Recipient recipient, @NonNull CharSequence body, @NonNull MessageSendType messageSendType, boolean hasQuote) { Permissions.with(fragment) .request(Manifest.permission.READ_EXTERNAL_STORAGE) .ifNecessary() .withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio)) - .onAllGranted(() -> fragment.startActivityForResult(MediaSelectionActivity.gallery(fragment.requireContext(), transport, Collections.emptyList(), recipient.getId(), body, hasQuote), requestCode)) + .onAllGranted(() -> fragment.startActivityForResult(MediaSelectionActivity.gallery(fragment.requireContext(), messageSendType, Collections.emptyList(), recipient.getId(), body, hasQuote), requestCode)) .execute(); } @@ -404,11 +404,11 @@ public class AttachmentManager { .execute(); } - public static void selectGif(Fragment fragment, int requestCode, RecipientId id, TransportOption selectedTransport, boolean isForMms, CharSequence textTrimmed) { + public static void selectGif(Fragment fragment, int requestCode, RecipientId id, MessageSendType sendType, boolean isForMms, CharSequence textTrimmed) { Intent intent = new Intent(fragment.requireContext(), GiphyActivity.class); intent.putExtra(GiphyActivity.EXTRA_IS_MMS, isForMms); intent.putExtra(GiphyActivity.EXTRA_RECIPIENT_ID, id); - intent.putExtra(GiphyActivity.EXTRA_TRANSPORT, selectedTransport); + intent.putExtra(GiphyActivity.EXTRA_TRANSPORT, sendType); intent.putExtra(GiphyActivity.EXTRA_TEXT, textTrimmed); fragment.startActivityForResult(intent, requestCode); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java index c19f6d74a9..8ad2cf787b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java @@ -16,9 +16,8 @@ import org.signal.core.util.BreakIteratorCompat; import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SimpleTask; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.TransportOption; -import org.thoughtcrime.securesms.TransportOptions; import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey; +import org.thoughtcrime.securesms.conversation.MessageSendType; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.Mention; @@ -98,13 +97,13 @@ public final class MultiShareSender { long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient); List mentions = getValidMentionsForRecipient(recipient, multiShareArgs.getMentions()); - TransportOption transport = resolveTransportOption(context, recipient); - boolean forceSms = recipient.isForceSmsSelection() && transport.isSms(); - int subscriptionId = transport.getSimSubscriptionId().orElse(-1); + MessageSendType sendType = resolveTransportOption(context, recipient); + boolean forceSms = recipient.isForceSmsSelection() && sendType.usesSmsTransport(); + int subscriptionId = sendType.getSimSubscriptionIdOr(-1); long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds()); - boolean needsSplit = !transport.isSms() && - message != null && - message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize; + boolean needsSplit = !sendType.usesSmsTransport() && + message != null && + message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize; boolean hasMmsMedia = !multiShareArgs.getMedia().isEmpty() || (multiShareArgs.getDataUri() != null && multiShareArgs.getDataUri() != Uri.EMPTY) || multiShareArgs.getStickerLocator() != null || @@ -119,8 +118,8 @@ public final class MultiShareSender { if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) { results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.MMS_NOT_ENABLED)); - } else if (hasMmsMedia && transport.isSms() || hasPushMedia && !transport.isSms() || canSendAsTextStory) { - sendMediaMessageOrCollectStoryToBatch(context, multiShareArgs, recipient, slideDeck, transport, threadId, forceSms, expiresIn, multiShareArgs.isViewOnce(), subscriptionId, mentions, recipientSearchKey.isStory(), sentTimestamp, canSendAsTextStory, storiesBatch); + } else if (hasMmsMedia && sendType.usesSmsTransport() || hasPushMedia && !sendType.usesSmsTransport() || canSendAsTextStory) { + sendMediaMessageOrCollectStoryToBatch(context, multiShareArgs, recipient, slideDeck, sendType, threadId, forceSms, expiresIn, multiShareArgs.isViewOnce(), subscriptionId, mentions, recipientSearchKey.isStory(), sentTimestamp, canSendAsTextStory, storiesBatch); results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.SUCCESS)); } else if (recipientSearchKey.isStory()) { results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.INVALID_SHARE_TO_STORY)); @@ -146,28 +145,26 @@ public final class MultiShareSender { return new MultiShareSendResultCollection(results); } - public static @NonNull TransportOption getWorstTransportOption(@NonNull Context context, @NonNull Set recipientSearchKeys) { + public static @NonNull MessageSendType getWorstTransportOption(@NonNull Context context, @NonNull Set recipientSearchKeys) { for (ContactSearchKey.RecipientSearchKey recipientSearchKey : recipientSearchKeys) { - TransportOption option = resolveTransportOption(context, Recipient.resolved(recipientSearchKey.getRecipientId()).isForceSmsSelection() && !recipientSearchKey.isStory()); - if (option.isSms()) { - return option; + MessageSendType type = resolveTransportOption(context, Recipient.resolved(recipientSearchKey.getRecipientId()).isForceSmsSelection() && !recipientSearchKey.isStory()); + if (type.usesSmsTransport()) { + return type; } } - return TransportOptions.getPushTransportOption(context); + return MessageSendType.SignalMessageSendType.INSTANCE; } - private static @NonNull TransportOption resolveTransportOption(@NonNull Context context, @NonNull Recipient recipient) { + private static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, @NonNull Recipient recipient) { return resolveTransportOption(context, !recipient.isDistributionList() && (recipient.isForceSmsSelection() || !recipient.isRegistered())); } - public static @NonNull TransportOption resolveTransportOption(@NonNull Context context, boolean forceSms) { + public static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, boolean forceSms) { if (forceSms) { - TransportOptions options = new TransportOptions(context, false); - options.setDefaultTransport(TransportOption.Type.SMS); - return options.getSelectedTransport(); + return MessageSendType.getFirstForTransport(context, false, MessageSendType.TransportType.SMS); } else { - return TransportOptions.getPushTransportOption(context); + return MessageSendType.SignalMessageSendType.INSTANCE; } } @@ -175,7 +172,7 @@ public final class MultiShareSender { @NonNull MultiShareArgs multiShareArgs, @NonNull Recipient recipient, @NonNull SlideDeck slideDeck, - @NonNull TransportOption transportOption, + @NonNull MessageSendType sendType, long threadId, boolean forceSms, long expiresIn, @@ -188,8 +185,8 @@ public final class MultiShareSender { @NonNull List storiesToBatchSend) { String body = multiShareArgs.getDraftText(); - if (transportOption.isType(TransportOption.Type.TEXTSECURE) && !forceSms && body != null) { - MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, transportOption.calculateCharacters(body).maxPrimaryMessageSize); + if (sendType.usesSignalTransport() && !forceSms && body != null) { + MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize); body = splitMessage.getBody(); if (splitMessage.getTextSlide().isPresent()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareRepository.kt index 1b6743ed80..a66083785f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareRepository.kt @@ -11,10 +11,9 @@ import androidx.core.content.ContextCompat import androidx.core.util.toKotlinPair import io.reactivex.rxjava3.core.Single import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.TransportOption -import org.thoughtcrime.securesms.TransportOptions import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.UriAttachment +import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendConstants import org.thoughtcrime.securesms.mms.MediaConstraints @@ -180,9 +179,8 @@ class ShareRepository(context: Context) { return false } - val options = TransportOptions(context, true) - options.setDefaultTransport(TransportOption.Type.SMS) - val mmsConstraints = MediaConstraints.getMmsMediaConstraints(options.selectedTransport.simSubscriptionId.orElse(-1)) + val sendType: MessageSendType = MessageSendType.getFirstForTransport(context, true, MessageSendType.TransportType.SMS) + val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1) return mmsConstraints.isSatisfied(context, attachment) || mmsConstraints.canResize(attachment) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java index 1d9fe124d0..645c51d658 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CharacterCalculator.java @@ -16,35 +16,13 @@ */ package org.thoughtcrime.securesms.util; -import android.os.Parcel; +import android.os.Parcelable; -import androidx.annotation.NonNull; -public abstract class CharacterCalculator { +public abstract class CharacterCalculator implements Parcelable { public abstract CharacterState calculateCharacters(String messageBody); - public static CharacterCalculator readFromParcel(@NonNull Parcel in) { - switch (in.readInt()) { - case 1: return new SmsCharacterCalculator(); - case 2: return new MmsCharacterCalculator(); - case 3: return new PushCharacterCalculator(); - default: throw new IllegalArgumentException("Read an unsupported value for a calculator."); - } - } - - public static void writeToParcel(@NonNull Parcel dest, @NonNull CharacterCalculator calculator) { - if (calculator instanceof SmsCharacterCalculator) { - dest.writeInt(1); - } else if (calculator instanceof MmsCharacterCalculator) { - dest.writeInt(2); - } else if (calculator instanceof PushCharacterCalculator) { - dest.writeInt(3); - } else { - throw new IllegalArgumentException("Tried to write an unsupported calculator to a parcel."); - } - } - public static class CharacterState { public final int charactersRemaining; public final int messagesSpent; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java index c25d47a845..4cf6c93975 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MmsCharacterCalculator.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.util; +import android.os.Parcel; + public class MmsCharacterCalculator extends CharacterCalculator { private static final int MAX_SIZE = 5000; @@ -8,4 +10,25 @@ public class MmsCharacterCalculator extends CharacterCalculator { public CharacterState calculateCharacters(String messageBody) { return new CharacterState(1, MAX_SIZE - messageBody.length(), MAX_SIZE, MAX_SIZE); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public static final Creator CREATOR = new Creator() { + @Override + public MmsCharacterCalculator createFromParcel(Parcel in) { + return new MmsCharacterCalculator(); + } + + @Override + public MmsCharacterCalculator[] newArray(int size) { + return new MmsCharacterCalculator[size]; + } + }; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java index 93561487c6..6799c28d2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/PushCharacterCalculator.java @@ -16,6 +16,8 @@ */ package org.thoughtcrime.securesms.util; +import android.os.Parcel; + public class PushCharacterCalculator extends CharacterCalculator { private static final int MAX_TOTAL_SIZE = 64 * 1024; private static final int MAX_PRIMARY_SIZE = 2000; @@ -23,5 +25,26 @@ public class PushCharacterCalculator extends CharacterCalculator { public CharacterState calculateCharacters(String messageBody) { return new CharacterState(1, MAX_TOTAL_SIZE - messageBody.length(), MAX_TOTAL_SIZE, MAX_PRIMARY_SIZE); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public static final Creator CREATOR = new Creator() { + @Override + public PushCharacterCalculator createFromParcel(Parcel in) { + return new PushCharacterCalculator(); + } + + @Override + public PushCharacterCalculator[] newArray(int size) { + return new PushCharacterCalculator[size]; + } + }; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java b/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java index 0d0172eff5..1068dc4e2e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SmsCharacterCalculator.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms.util; +import android.os.Parcel; import android.telephony.SmsMessage; import org.signal.core.util.logging.Log; @@ -62,4 +63,25 @@ public class SmsCharacterCalculator extends CharacterCalculator { return new CharacterState(messagesSpent, charactersRemaining, maxMessageSize, maxMessageSize); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public static final Creator CREATOR = new Creator() { + @Override + public SmsCharacterCalculator createFromParcel(Parcel in) { + return new SmsCharacterCalculator(); + } + + @Override + public SmsCharacterCalculator[] newArray(int size) { + return new SmsCharacterCalculator[size]; + } + }; } diff --git a/app/src/main/res/drawable/ic_insecure_24.xml b/app/src/main/res/drawable/ic_insecure_24.xml new file mode 100644 index 0000000000..8ff16fc124 --- /dev/null +++ b/app/src/main/res/drawable/ic_insecure_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_secure_24.xml b/app/src/main/res/drawable/ic_secure_24.xml new file mode 100644 index 0000000000..75a644c7b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_secure_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6aa70af46f..f36b5d474f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -259,8 +259,12 @@ Calls not supported This device does not appear to support dial actions. Insecure SMS + + Insecure SMS (%1$s) Insecure MMS - Signal + + Insecure MMS (%1$s) + Signal message Let\'s switch to Signal %1$s Please choose a contact Unblock diff --git a/qr/lib/src/main/java/org/signal/qr/ScannerView21.kt b/qr/lib/src/main/java/org/signal/qr/ScannerView21.kt index 93486486a2..9de2cc5700 100644 --- a/qr/lib/src/main/java/org/signal/qr/ScannerView21.kt +++ b/qr/lib/src/main/java/org/signal/qr/ScannerView21.kt @@ -2,30 +2,22 @@ package org.signal.qr import android.annotation.SuppressLint import android.content.Context -import android.graphics.Bitmap -import android.graphics.ImageFormat import android.util.Size import android.widget.FrameLayout import androidx.annotation.RequiresApi -import androidx.camera.core.AspectRatio import androidx.camera.core.Camera import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageProxy import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.content.ContextCompat -import androidx.core.math.MathUtils.clamp import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import io.reactivex.rxjava3.subjects.PublishSubject import org.signal.core.util.logging.Log import org.signal.qr.kitkat.ScanListener -import java.nio.ByteBuffer import java.util.concurrent.Executors - /** * API21+ version of QR scanning view. Uses camerax APIs. */