Remove more SMS vestiges.

This commit is contained in:
Greyson Parrelli
2024-03-12 13:47:18 -04:00
parent 6754fef164
commit 825ca0d737
94 changed files with 60 additions and 8017 deletions

View File

@@ -198,7 +198,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(() -> ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary())
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
.addPostRender(this::initializeExpiringMessageManager)
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
.addPostRender(this::initializeTrimThreadsByDateManager)
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))

View File

@@ -73,8 +73,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
@Override
protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
boolean includeSms = Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().allowSmsFeatures();
int displayMode = includeSms ? ContactSelectionDisplayMode.FLAG_ALL : ContactSelectionDisplayMode.FLAG_PUSH | ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_INACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_SELF;
int displayMode = ContactSelectionDisplayMode.FLAG_PUSH | ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_INACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_SELF;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
}

View File

@@ -119,14 +119,9 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
if (Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported()) {
shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener());
} else {
smsButton.setVisibility(View.GONE);
shareText.setText(R.string.InviteActivity_share);
shareButton.setOnClickListener(new ShareClickListener());
}
smsButton.setVisibility(View.GONE);
shareText.setText(R.string.InviteActivity_share);
shareButton.setOnClickListener(new ShareClickListener());
}
private Animation loadAnimation(@AnimRes int animResId) {
@@ -200,13 +195,6 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
}
}
private class SmsClickListener implements OnClickListener {
@Override
public void onClick(View v) {
ViewUtil.animateIn(smsSendFrame, slideInAnimation);
}
}
private class SmsCancelClickListener implements OnClickListener {
@Override
public void onClick(View v) {

View File

@@ -121,8 +121,6 @@ public class NewConversationActivity extends ContactSelectionActivity
@Override
public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
boolean smsSupported = SignalStore.misc().getSmsExportPhase().allowSmsFeatures();
if (recipientId.isPresent()) {
launch(Recipient.resolved(recipientId.get()));
} else {
@@ -138,7 +136,7 @@ public class NewConversationActivity extends ContactSelectionActivity
if (result instanceof RecipientRepository.LookupResult.Success) {
Recipient resolved = Recipient.resolved(((RecipientRepository.LookupResult.Success) result).getRecipientId());
if (smsSupported || resolved.isRegistered() && resolved.hasServiceId()) {
if (resolved.isRegistered() && resolved.hasServiceId()) {
launch(resolved);
}
} else if (result instanceof RecipientRepository.LookupResult.NotFound || result instanceof RecipientRepository.LookupResult.InvalidEntry) {
@@ -153,8 +151,6 @@ public class NewConversationActivity extends ContactSelectionActivity
.show();
}
});
} else if (smsSupported) {
launch(Recipient.external(this, number));
}
}
@@ -304,7 +300,7 @@ public class NewConversationActivity extends ContactSelectionActivity
return null;
}
if (recipient.isRegistered() || (SignalStore.misc().getSmsExportPhase().allowSmsFeatures())) {
if (recipient.isRegistered()) {
return new ActionItem(
R.drawable.ic_phone_right_24,
getString(R.string.NewConversationActivity__audio_call),

View File

@@ -38,7 +38,6 @@ class GiftFlowRecipientSelectionFragment : Fragment(R.layout.gift_flow_recipient
R.id.multiselect_container,
MultiselectForwardFragment.create(
MultiselectForwardFragmentArgs(
canSendToNonPush = false,
multiShareArgs = emptyList(),
forceDisableAddMessage = true,
selectSingleRecipient = true

View File

@@ -467,7 +467,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
is CallLogDeletionResult.FailedToRevoke -> {
errorDialog = MaterialAlertDialogBuilder(requireContext())
.setMessage(resources.getQuantityString(R.plurals.CallLogFragment__cant_delete_call_link, it.failedRevocations))
.setPositiveButton(R.string.ok, null)
.setPositiveButton(android.R.string.ok, null)
.show()
}
CallLogDeletionResult.Success -> {

View File

@@ -99,7 +99,7 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
}
private fun showErrorDialog(context: Context, @StringRes message: Int, onPositiveButtonClickListener: OnClickListener?) {
MaterialAlertDialogBuilder(context).setMessage(message).setPositiveButton(R.string.ok, onPositiveButtonClickListener).show()
MaterialAlertDialogBuilder(context).setMessage(message).setPositiveButton(android.R.string.ok, onPositiveButtonClickListener).show()
}
private sealed interface RequestCodeResult {

View File

@@ -16,9 +16,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ContactFilterView
import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode
import org.thoughtcrime.securesms.groups.SelectionLimits
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
import java.util.Optional
@@ -100,17 +98,12 @@ class SelectRecipientsFragment : LoggingFragment(), ContactSelectionListFragment
}
private fun getDefaultDisplayMode(): Int {
var mode = ContactSelectionDisplayMode.FLAG_PUSH or
return ContactSelectionDisplayMode.FLAG_PUSH or
ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS or
ContactSelectionDisplayMode.FLAG_HIDE_NEW or
ContactSelectionDisplayMode.FLAG_HIDE_RECENT_HEADER or
ContactSelectionDisplayMode.FLAG_GROUPS_AFTER_CONTACTS
if (Util.isDefaultSmsProvider(requireContext()) && SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
mode = mode or ContactSelectionDisplayMode.FLAG_SMS
}
return mode or ContactSelectionDisplayMode.FLAG_HIDE_GROUPS_V1
ContactSelectionDisplayMode.FLAG_GROUPS_AFTER_CONTACTS or
ContactSelectionDisplayMode.FLAG_HIDE_GROUPS_V1
}
override fun onBeforeContactSelected(isFromUnknownSearchKey: Boolean, recipientId: Optional<RecipientId>, number: String?, callback: Consumer<Boolean>) {

View File

@@ -1,12 +0,0 @@
package org.thoughtcrime.securesms.components.settings.app.wrapped
import androidx.fragment.app.Fragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.preferences.MmsPreferencesFragment
class WrappedMmsPreferencesFragment : SettingsWrapperFragment() {
override fun getFragment(): Fragment {
toolbar.setTitle(R.string.preferences__advanced_mms_access_point_names)
return MmsPreferencesFragment()
}
}

View File

@@ -157,7 +157,7 @@ sealed class ConversationSettingsViewModel(
}
store.update(liveRecipient.liveData) { recipient, state ->
val isAudioAvailable = (recipient.isRegistered || SignalStore.misc().smsExportPhase.allowSmsFeatures()) &&
val isAudioAvailable = recipient.isRegistered &&
!recipient.isGroup &&
!recipient.isBlocked &&
!recipient.isSelf &&

View File

@@ -79,10 +79,6 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
try {
ContactDiscovery.refresh(context, recipients, true);
if (Util.isDefaultSmsProvider(context)) {
ContactDiscovery.syncRecipientInfoWithSystemContacts(context);
}
} catch (IOException e) {
Log.w(TAG, "Failed to refresh! Scheduling for later.", e);
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(true));

View File

@@ -54,7 +54,6 @@ import androidx.annotation.ColorInt;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.media3.common.MediaItem;
@@ -107,8 +106,6 @@ import org.thoughtcrime.securesms.conversation.v2.items.InteractiveConversationE
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemUtils;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.MediaTable;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
@@ -118,8 +115,6 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
@@ -2687,8 +2682,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (eventListener != null) {
eventListener.onIncomingIdentityMismatchClicked(messageRecord.getFromRecipient().getId());
}
} else if (messageRecord.isPendingInsecureSmsFallback()) {
handleMessageApproval();
}
}
}
@@ -2782,45 +2775,4 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
footer.setAudioDuration(durationMillis, playheadMillis);
}
}
private void handleMessageApproval() {
final int title;
final int message;
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_dialog_title;
else title = R.string.ConversationItem_click_to_approve_unencrypted_sms_dialog_title;
message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message;
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(title);
if (message > -1) builder.setMessage(message);
builder.setPositiveButton(R.string.yes, (dialogInterface, i) -> {
MessageTable db = SignalDatabase.messages();
db.markAsInsecure(messageRecord.getId());
db.markAsOutbox(messageRecord.getId());
db.markAsForcedSms(messageRecord.getId());
if (messageRecord.isMms()) {
MmsSendJob.enqueue(context,
ApplicationDependencies.getJobManager(),
messageRecord.getId());
} else {
ApplicationDependencies.getJobManager().add(new SmsSendJob(messageRecord.getId(),
messageRecord.getToRecipient()));
}
});
builder.setNegativeButton(R.string.no, (dialogInterface, i) -> {
if (messageRecord.isMms()) {
SignalDatabase.messages().markAsSentFailed(messageRecord.getId());
} else {
SignalDatabase.messages().markAsSentFailed(messageRecord.getId());
}
});
builder.show();
}
}

View File

@@ -8,9 +8,7 @@ import androidx.annotation.StringRes
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.R
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 java.lang.IllegalArgumentException
/**
@@ -29,15 +27,9 @@ sealed class MessageSendType(
@ColorRes
val backgroundColorRes: Int,
val transportType: TransportType,
val characterCalculator: CharacterCalculator,
open val simName: CharSequence? = null,
open val simSubscriptionId: Int? = null
val characterCalculator: CharacterCalculator
) : Parcelable {
@get:JvmName("usesSmsTransport")
val usesSmsTransport
get() = transportType == TransportType.SMS
@get:JvmName("usesSignalTransport")
val usesSignalTransport
get() = transportType == TransportType.SIGNAL
@@ -50,54 +42,6 @@ sealed class MessageSendType(
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.
*/

View File

@@ -1,20 +1,9 @@
package org.thoughtcrime.securesms.conversation.mutiselect
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import androidx.core.content.ContextCompat
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.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.mms.TextSlide
import org.thoughtcrime.securesms.util.Util
/**
* General helper object for all things multiselect. This is only utilized by
@@ -65,51 +54,4 @@ object Multiselect {
return parts
}
fun canSendToNonPush(context: Context, multiselectPart: MultiselectPart): Boolean {
return when (multiselectPart) {
is MultiselectPart.Attachments -> canSendAllAttachmentsToNonPush(context, multiselectPart.conversationMessage.messageRecord)
is MultiselectPart.Message -> canSendAllAttachmentsToNonPush(context, multiselectPart.conversationMessage.messageRecord)
is MultiselectPart.Text -> true
is MultiselectPart.Update -> throw AssertionError("Should never get to here.")
}
}
/**
* Helper function to determine whether a given attachment can be sent via MMS.
*/
fun isMmsSupported(context: Context, mediaUri: Uri, mediaType: String, mediaSize: Long): Boolean {
val canReadPhoneState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
if (!Util.isDefaultSmsProvider(context) || !canReadPhoneState || !Util.isMmsCapable(context) || !SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
return false
}
val sendType: MessageSendType = MessageSendType.getFirstForTransport(MessageSendType.TransportType.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1)
return mmsConstraints.isSatisfied(context, mediaUri, mediaType, mediaSize) || mmsConstraints.canResize(mediaType)
}
private fun canSendAllAttachmentsToNonPush(context: Context, messageRecord: MessageRecord): Boolean {
return if (messageRecord is MmsMessageRecord) {
messageRecord.slideDeck.asAttachments().all { isMmsSupported(context, it) }
} else {
true
}
}
/**
* Helper function to determine whether a given attachment can be sent via MMS.
*/
private fun isMmsSupported(context: Context, attachment: Attachment): Boolean {
val canReadPhoneState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
if (!Util.isDefaultSmsProvider(context) || !canReadPhoneState || !Util.isMmsCapable(context) || !SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
return false
}
val sendType: MessageSendType = MessageSendType.getFirstForTransport(MessageSendType.TransportType.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1)
return mmsConstraints.isSatisfied(context, attachment) || mmsConstraints.canResize(attachment)
}
}

View File

@@ -59,7 +59,6 @@ import org.thoughtcrime.securesms.stories.settings.privacy.ChooseInitialMyStoryM
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FullscreenHelper
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.fragments.findListener
import org.thoughtcrime.securesms.util.fragments.requireListener
@@ -470,7 +469,7 @@ class MultiselectForwardFragment :
addSection(
ContactSearchConfiguration.Section.Individuals(
includeHeader = true,
transportType = if (includeSms()) ContactSearchConfiguration.TransportType.ALL else ContactSearchConfiguration.TransportType.PUSH,
transportType = ContactSearchConfiguration.TransportType.PUSH,
includeSelf = true
)
)
@@ -485,18 +484,13 @@ class MultiselectForwardFragment :
addSection(
ContactSearchConfiguration.Section.Groups(
includeHeader = true,
includeMms = includeSms()
includeHeader = true
)
)
}
}
}
private fun includeSms(): Boolean {
return Util.isDefaultSmsProvider(requireContext()) && args.canSendToNonPush
}
private fun isSelectedMediaValidForStories(): Boolean {
return !args.isViewOnce && args.multiShareArgs.all { it.isValidForStories }
}

View File

@@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.color.ViewColorSet
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.MessageStyler
import org.thoughtcrime.securesms.conversation.mutiselect.Multiselect
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@@ -23,7 +22,6 @@ import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.sharing.MultiShareArgs
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.hasSharedContact
import java.util.Optional
import java.util.function.Consumer
@@ -41,7 +39,6 @@ import java.util.function.Consumer
*/
@Parcelize
data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
val canSendToNonPush: Boolean,
val multiShareArgs: List<MultiShareArgs> = listOf(),
@StringRes val title: Int = R.string.MultiselectForwardFragment__forward_to,
val forceDisableAddMessage: Boolean = false,
@@ -60,8 +57,6 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
@JvmStatic
fun create(context: Context, threadId: Long, mediaUri: Uri, mediaType: String, consumer: Consumer<MultiselectForwardFragmentArgs>) {
SignalExecutors.BOUNDED.execute {
val mediaSize = MediaUtil.getMediaSize(context, mediaUri)
val isMmsSupported = Multiselect.isMmsSupported(context, mediaUri, mediaType, mediaSize)
val multiShareArgs = MultiShareArgs.Builder(setOf())
.withDataUri(mediaUri)
.withDataType(mediaType)
@@ -76,7 +71,6 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
ThreadUtil.runOnMain {
consumer.accept(
MultiselectForwardFragmentArgs(
isMmsSupported,
listOf(multiShareArgs),
storySendRequirements = Stories.MediaTransform.SendRequirements.CAN_NOT_SEND,
sendButtonColors = sendButtonColors ?: ViewColorSet.PRIMARY
@@ -97,13 +91,11 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
throw AssertionError("Cannot forward view once media")
}
val canSendToNonPush: Boolean = selectedParts.all { Multiselect.canSendToNonPush(context, it) }
val multiShareArgs: List<MultiShareArgs> = conversationMessages.map { buildMultiShareArgs(context, it, selectedParts) }
ThreadUtil.runOnMain {
consumer.accept(
MultiselectForwardFragmentArgs(
canSendToNonPush,
multiShareArgs,
storySendRequirements = Stories.MediaTransform.SendRequirements.CAN_NOT_SEND
)

View File

@@ -28,7 +28,7 @@ object ConversationDialogs {
fun displayCannotStartGroupCallDueToPermissionsDialog(context: Context) {
MaterialAlertDialogBuilder(context).setTitle(R.string.ConversationActivity_cant_start_group_call)
.setMessage(R.string.ConversationActivity_only_admins_of_this_group_can_start_a_call)
.setPositiveButton(R.string.ok) { d: DialogInterface, w: Int -> d.dismiss() }
.setPositiveButton(android.R.string.ok) { d: DialogInterface, w: Int -> d.dismiss() }
.show()
}

View File

@@ -1,174 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.text.TextUtils;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.mms.LegacyMmsConnection.Apn;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
/**
* Database to query APN and MMSC information
*/
public class ApnDatabase {
private static final String TAG = Log.tag(ApnDatabase.class);
private final SQLiteDatabase db;
private final Context context;
private static final String DATABASE_NAME = "apns.db";
private static final String ASSET_PATH = "databases" + File.separator + DATABASE_NAME;
private static final String TABLE_NAME = "apns";
private static final String ID_COLUMN = "_id";
private static final String MCC_MNC_COLUMN = "mccmnc";
private static final String MCC_COLUMN = "mcc";
private static final String MNC_COLUMN = "mnc";
private static final String CARRIER_COLUMN = "carrier";
private static final String APN_COLUMN = "apn";
private static final String MMSC_COLUMN = "mmsc";
private static final String PORT_COLUMN = "port";
private static final String TYPE_COLUMN = "type";
private static final String PROTOCOL_COLUMN = "protocol";
private static final String BEARER_COLUMN = "bearer";
private static final String ROAMING_PROTOCOL_COLUMN = "roaming_protocol";
private static final String CARRIER_ENABLED_COLUMN = "carrier_enabled";
private static final String MMS_PROXY_COLUMN = "mmsproxy";
private static final String MMS_PORT_COLUMN = "mmsport";
private static final String PROXY_COLUMN = "proxy";
private static final String MVNO_MATCH_DATA_COLUMN = "mvno_match_data";
private static final String MVNO_TYPE_COLUMN = "mvno";
private static final String AUTH_TYPE_COLUMN = "authtype";
private static final String USER_COLUMN = "user";
private static final String PASSWORD_COLUMN = "password";
private static final String SERVER_COLUMN = "server";
private static final String BASE_SELECTION = MCC_MNC_COLUMN + " = ?";
private static ApnDatabase instance = null;
public synchronized static ApnDatabase getInstance(Context context) throws IOException {
if (instance == null) instance = new ApnDatabase(context.getApplicationContext());
return instance;
}
private ApnDatabase(final Context context) throws IOException {
this.context = context;
File dbFile = context.getDatabasePath(DATABASE_NAME);
if (!dbFile.getParentFile().exists() && !dbFile.getParentFile().mkdir()) {
throw new IOException("couldn't make databases directory");
}
StreamUtil.copy(context.getAssets().open(ASSET_PATH, AssetManager.ACCESS_STREAMING),
new FileOutputStream(dbFile));
try {
this.db = SQLiteDatabase.openDatabase(context.getDatabasePath(DATABASE_NAME).getPath(),
null,
SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
} catch (SQLiteException e) {
throw new IOException(e);
}
}
private Apn getCustomApnParameters() {
String mmsc = TextSecurePreferences.getMmscUrl(context).trim();
if (!TextUtils.isEmpty(mmsc) && !mmsc.startsWith("http"))
mmsc = "http://" + mmsc;
String proxy = TextSecurePreferences.getMmscProxy(context);
String port = TextSecurePreferences.getMmscProxyPort(context);
String user = TextSecurePreferences.getMmscUsername(context);
String pass = TextSecurePreferences.getMmscPassword(context);
return new Apn(mmsc, proxy, port, user, pass);
}
public Apn getDefaultApnParameters(String mccmnc, String apn) {
if (mccmnc == null) {
Log.w(TAG, "mccmnc was null, returning null");
return Apn.EMPTY;
}
Cursor cursor = null;
try {
if (apn != null) {
Log.d(TAG, "Querying table for MCC+MNC " + mccmnc + " and APN name " + apn);
cursor = db.query(TABLE_NAME, null,
BASE_SELECTION + " AND " + APN_COLUMN + " = ?",
new String[] {mccmnc, apn},
null, null, null);
}
if (cursor == null || !cursor.moveToFirst()) {
if (cursor != null) cursor.close();
Log.d(TAG, "Querying table for MCC+MNC " + mccmnc + " without APN name");
cursor = db.query(TABLE_NAME, null,
BASE_SELECTION,
new String[] {mccmnc},
null, null, null);
}
if (cursor != null && cursor.moveToFirst()) {
Apn params = new Apn(cursor.getString(cursor.getColumnIndexOrThrow(MMSC_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(MMS_PROXY_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(MMS_PORT_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(USER_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD_COLUMN)));
Log.d(TAG, "Returning preferred APN " + params);
return params;
}
Log.w(TAG, "No matching APNs found, returning null");
return Apn.EMPTY;
} finally {
if (cursor != null) cursor.close();
}
}
public Optional<Apn> getMmsConnectionParameters(String mccmnc, String apn) {
Apn customApn = getCustomApnParameters();
Apn defaultApn = getDefaultApnParameters(mccmnc, apn);
Apn result = new Apn(customApn, defaultApn,
TextSecurePreferences.getUseCustomMmsc(context),
TextSecurePreferences.getUseCustomMmscProxy(context),
TextSecurePreferences.getUseCustomMmscProxyPort(context),
TextSecurePreferences.getUseCustomMmscUsername(context),
TextSecurePreferences.getUseCustomMmscPassword(context));
if (TextUtils.isEmpty(result.getMmsc())) return Optional.empty();
else return Optional.of(result);
}
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import org.thoughtcrime.securesms.util.Util;
public class ContentValuesBuilder {
private final ContentValues contentValues;
public ContentValuesBuilder(ContentValues contentValues) {
this.contentValues = contentValues;
}
public void add(String key, String charsetKey, EncodedStringValue value) {
if (value != null) {
contentValues.put(key, Util.toIsoString(value.getTextString()));
contentValues.put(charsetKey, value.getCharacterSet());
}
}
public void add(String contentKey, byte[] value) {
if (value != null) {
contentValues.put(contentKey, Util.toIsoString(value));
}
}
public void add(String contentKey, int b) {
if (b != 0)
contentValues.put(contentKey, b);
}
public void add(String contentKey, long value) {
if (value != -1L)
contentValues.put(contentKey, value);
}
public ContentValues getContentValues() {
return contentValues;
}
}

View File

@@ -23,7 +23,6 @@ import android.text.SpannableString
import android.text.TextUtils
import androidx.annotation.VisibleForTesting
import androidx.core.content.contentValuesOf
import com.google.android.mms.pdu_alt.PduHeaders
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
@@ -305,7 +304,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
READ,
MMS_CONTENT_LOCATION,
MMS_EXPIRY,
MMS_MESSAGE_TYPE,
MMS_MESSAGE_SIZE,
MMS_STATUS,
MMS_TRANSACTION_ID,
@@ -2506,7 +2504,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
FROM_RECIPIENT_ID to retrieved.from.serialize(),
TO_RECIPIENT_ID to Recipient.self().id.serialize(),
TYPE to type,
MMS_MESSAGE_TYPE to PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF,
THREAD_ID to threadId,
MMS_STATUS to MmsStatus.DOWNLOAD_INITIALIZED,
DATE_RECEIVED to retrieved.receivedTimeMillis,
@@ -2678,17 +2675,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
TrimThreadJob.enqueueAsync(threadId)
}
fun markIncomingNotificationReceived(threadId: Long) {
notifyConversationListeners(threadId)
if (Util.isDefaultSmsProvider(context)) {
threads.incrementUnread(threadId, 1, 0)
}
threads.update(threadId, true)
TrimThreadJob.enqueueAsync(threadId)
}
fun markGiftRedemptionCompleted(messageId: Long) {
markGiftRedemptionState(messageId, GiftBadge.RedemptionState.REDEEMED)
}
@@ -2874,7 +2860,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val contentValues = ContentValues()
contentValues.put(DATE_SENT, message.sentTimeMillis)
contentValues.put(MMS_MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ)
contentValues.put(TYPE, type)
contentValues.put(THREAD_ID, threadId)
contentValues.put(READ, 1)
@@ -4919,13 +4904,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val insertedAttachments: Map<Attachment, AttachmentId>? = null
)
data class MmsNotificationInfo(
val from: RecipientId,
val contentLocation: String,
val transactionId: String,
val subscriptionId: Int
)
data class MessageReceiptUpdate(
val threadId: Long,
val messageId: MessageId,
@@ -5043,8 +5021,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
override fun getCurrent(): MessageRecord {
val mmsType = cursor.requireLong(MMS_MESSAGE_TYPE)
return getMediaMmsMessageRecord(cursor)
}

View File

@@ -266,8 +266,7 @@ public abstract class MessageRecord extends DisplayRecord {
throw new AssertionError(e);
}
} else if (isSmsExportType()) {
int messageResource = SignalStore.misc().getSmsExportPhase().isSmsSupported() ? R.string.MessageRecord__you_will_no_longer_be_able_to_send_sms_messages_from_signal_soon
: R.string.MessageRecord__you_can_no_longer_send_sms_messages_in_signal;
int messageResource = R.string.MessageRecord__you_can_no_longer_send_sms_messages_in_signal;
return fromRecipient(getFromRecipient(), r -> context.getString(messageResource, r.getDisplayName(context)), R.drawable.ic_update_info_16);
} else if (isPaymentsRequestToActivate()) {
return isOutgoing() ? fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_you_sent_request, r.getShortDisplayName(context)), R.drawable.ic_card_activate_payments)

View File

@@ -59,9 +59,7 @@ public class CreateGroupActivity extends ContactSelectionActivity implements Con
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
intent.putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.create_group_activity);
boolean smsEnabled = SignalStore.misc().getSmsExportPhase().allowSmsFeatures();
int displayMode = smsEnabled ? ContactSelectionDisplayMode.FLAG_SMS | ContactSelectionDisplayMode.FLAG_PUSH
: ContactSelectionDisplayMode.FLAG_PUSH;
int displayMode = ContactSelectionDisplayMode.FLAG_PUSH;
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, FeatureFlags.groupLimits().excludingSelf());

View File

@@ -162,8 +162,7 @@ public final class AttachmentCompressionJob extends BaseJob {
return;
}
MediaConstraints mediaConstraints = mms ? MediaConstraints.getMmsMediaConstraints(mmsSubscriptionId)
: MediaConstraints.getPushMediaConstraints(SentMediaQuality.fromCode(transformProperties.sentMediaQuality));
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(SentMediaQuality.fromCode(transformProperties.sentMediaQuality));
compress(database, mediaConstraints, databaseAttachment);
}

View File

@@ -69,7 +69,7 @@ public class ConversationShortcutUpdateJob extends BaseJob {
int maxShortcuts = ConversationUtil.getMaxShortcuts(context);
List<Recipient> ranked = new ArrayList<>(maxShortcuts);
try (ThreadTable.Reader reader = threadTable.readerFor(threadTable.getRecentConversationList(maxShortcuts, false, false, false, true, !Util.isDefaultSmsProvider(context), false))) {
try (ThreadTable.Reader reader = threadTable.readerFor(threadTable.getRecentConversationList(maxShortcuts, false, false, false, true, true, false))) {
ThreadRecord record;
while ((record = reader.getNext()) != null) {
ranked.add(record.getRecipient().resolve());

View File

@@ -139,7 +139,6 @@ public final class JobManagerFactories {
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
put(LocalBackupJobApi29.KEY, new LocalBackupJobApi29.Factory());
put(MarkerJob.KEY, new MarkerJob.Factory());
put(MmsSendJob.KEY, new MmsSendJob.Factory());
put(MultiDeviceBlockedUpdateJob.KEY, new MultiDeviceBlockedUpdateJob.Factory());
put(MultiDeviceCallLinkSyncJob.KEY, new MultiDeviceCallLinkSyncJob.Factory());
put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory());
@@ -206,8 +205,6 @@ public final class JobManagerFactories {
put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory());
put(ResetSvrGuessCountJob.KEY, new ResetSvrGuessCountJob.Factory());
put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory());
put(SmsSendJob.KEY, new SmsSendJob.Factory());
put(SmsSentJob.KEY, new SmsSentJob.Factory());
put(StickerDownloadJob.KEY, new StickerDownloadJob.Factory());
put(StickerPackDownloadJob.KEY, new StickerPackDownloadJob.Factory());
put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory());
@@ -312,6 +309,9 @@ public final class JobManagerFactories {
put("StoryReadStateMigrationJob", new PassingMigrationJob.Factory());
put("GroupV1MigrationJob", new FailingJob.Factory());
put("NewRegistrationUsernameSyncJob", new FailingJob.Factory());
put("SmsSendJob", new FailingJob.Factory());
put("SmsSentJob", new FailingJob.Factory());
put("MmsSendJobV2", new FailingJob.Factory());
}};
}

View File

@@ -1,371 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.mms.dom.smil.parser.SmilXmlSerializer;
import com.annimon.stream.Stream;
import com.google.android.mms.ContentType;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import com.google.android.mms.pdu_alt.PduBody;
import com.google.android.mms.pdu_alt.PduComposer;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduPart;
import com.google.android.mms.pdu_alt.SendConf;
import com.google.android.mms.pdu_alt.SendReq;
import com.google.android.mms.smil.SmilHelper;
import com.klinker.android.send_message.Utils;
import org.signal.core.util.Hex;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobLogger;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
public final class MmsSendJob extends SendJob {
public static final String KEY = "MmsSendJobV2";
private static final String TAG = Log.tag(MmsSendJob.class);
private static final String KEY_MESSAGE_ID = "message_id";
private final long messageId;
private MmsSendJob(long messageId) {
this(new Job.Parameters.Builder()
.setQueue("mms-operation")
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(15)
.build(),
messageId);
}
/** Enqueues compression jobs for attachments and finally the MMS send job. */
@WorkerThread
public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId) {
MessageTable database = SignalDatabase.messages();
OutgoingMessage message;
try {
message = database.getOutgoingMessage(messageId);
} catch (MmsException | NoSuchMessageException e) {
throw new AssertionError(e);
}
List<Job> compressionJobs = Stream.of(message.getAttachments())
.map(a -> (Job) AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, true, -1))
.toList();
MmsSendJob sendJob = new MmsSendJob(messageId);
jobManager.startChain(compressionJobs)
.then(sendJob)
.enqueue();
}
private MmsSendJob(@NonNull Job.Parameters parameters, long messageId) {
super(parameters);
this.messageId = messageId;
}
@Override
public @Nullable byte[] serialize() {
return new JsonJobData.Builder().putLong(KEY_MESSAGE_ID, messageId).serialize();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
SignalDatabase.messages().markAsSending(messageId);
}
@Override
public void onSend() throws MmsException, NoSuchMessageException, IOException {
MessageTable database = SignalDatabase.messages();
OutgoingMessage message = database.getOutgoingMessage(messageId);
if (database.isSent(messageId)) {
Log.w(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
}
try {
Log.i(TAG, "Sending message: " + messageId);
SendReq pdu = constructSendPdu(message);
validateDestinations(message, pdu);
final byte[] pduBytes = getPduBytes(pdu);
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, -1);
final MmsSendResult result = getSendResult(sendConf, pdu);
database.markAsSent(messageId, false);
markAttachmentsUploaded(messageId, message);
Log.i(TAG, "Sent message: " + messageId);
} catch (UndeliverableMessageException | IOException e) {
Log.w(TAG, e);
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
} catch (InsecureFallbackApprovalException e) {
Log.w(TAG, e);
database.markAsPendingInsecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
return false;
}
@Override
public void onFailure() {
Log.i(TAG, JobLogger.format(this, "onFailure() messageId: " + messageId));
SignalDatabase.messages().markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
private byte[] getPduBytes(SendReq message)
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
{
byte[] pduBytes = new PduComposer(context, message).make();
if (pduBytes == null) {
throw new UndeliverableMessageException("PDU composition failed, null payload");
}
return pduBytes;
}
private MmsSendResult getSendResult(SendConf conf, SendReq message)
throws UndeliverableMessageException
{
if (conf == null) {
throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
} else if (isInconsistentResponse(message, conf)) {
throw new UndeliverableMessageException("Mismatched response!");
} else {
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
}
}
private boolean isInconsistentResponse(SendReq message, SendConf response) {
Log.i(TAG, "Comparing: " + Hex.toString(message.getTransactionId()));
Log.i(TAG, "With: " + Hex.toString(response.getTransactionId()));
return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
}
private void validateDestinations(EncodedStringValue[] destinations) throws UndeliverableMessageException {
if (destinations == null) return;
for (EncodedStringValue destination : destinations) {
if (destination == null || !NumberUtil.isValidSmsOrEmail(destination.getString())) {
throw new UndeliverableMessageException("Invalid destination: " +
(destination == null ? null : destination.getString()));
}
}
}
private void validateDestinations(OutgoingMessage media, SendReq message) throws UndeliverableMessageException {
validateDestinations(message.getTo());
validateDestinations(message.getCc());
validateDestinations(message.getBcc());
if (message.getTo() == null && message.getCc() == null && message.getBcc() == null) {
throw new UndeliverableMessageException("No to, cc, or bcc specified!");
}
if (media.isSecure()) {
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
}
}
private SendReq constructSendPdu(OutgoingMessage message)
throws UndeliverableMessageException
{
SendReq req = new SendReq();
String lineNumber = getMyNumber(context);
MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(-1);
List<Attachment> scaledAttachments = message.getAttachments();
if (!TextUtils.isEmpty(lineNumber)) {
req.setFrom(new EncodedStringValue(lineNumber));
} else {
req.setFrom(new EncodedStringValue(SignalStore.account().getE164()));
}
if (message.getThreadRecipient().isMmsGroup()) {
List<Recipient> members = SignalDatabase.groups().getGroupMembers(message.getThreadRecipient().requireGroupId(), GroupTable.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
for (Recipient member : members) {
if (!member.hasSmsAddress()) {
throw new UndeliverableMessageException("One of the group recipients did not have an SMS address! " + member.getId());
}
if (message.getDistributionType() == ThreadTable.DistributionTypes.BROADCAST) {
req.addBcc(new EncodedStringValue(member.requireSmsAddress()));
} else {
req.addTo(new EncodedStringValue(member.requireSmsAddress()));
}
}
} else {
if (!message.getThreadRecipient().hasSmsAddress()) {
throw new UndeliverableMessageException("Recipient did not have an SMS address! " + message.getThreadRecipient().getId());
}
req.addTo(new EncodedStringValue(message.getThreadRecipient().requireSmsAddress()));
}
req.setDate(System.currentTimeMillis() / 1000);
PduBody body = new PduBody();
int size = 0;
if (!TextUtils.isEmpty(message.getBody())) {
PduPart part = new PduPart();
String name = String.valueOf(System.currentTimeMillis());
part.setData(Util.toUtf8Bytes(message.getBody()));
part.setCharset(CharacterSets.UTF_8);
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
part.setContentId(name.getBytes());
part.setContentLocation((name + ".txt").getBytes());
part.setName((name + ".txt").getBytes());
body.addPart(part);
size += getPartSize(part);
}
for (Attachment attachment : scaledAttachments) {
try {
if (attachment.getUri() == null) throw new IOException("Assertion failed, attachment for outgoing MMS has no data!");
String fileName = attachment.fileName;
PduPart part = new PduPart();
if (fileName == null) {
fileName = String.valueOf(Math.abs(new SecureRandom().nextLong()));
String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(attachment.contentType);
if (fileExtension != null) fileName = fileName + "." + fileExtension;
}
if (attachment.contentType.startsWith("text")) {
part.setCharset(CharacterSets.UTF_8);
}
part.setContentType(attachment.contentType.getBytes());
part.setContentLocation(fileName.getBytes());
part.setName(fileName.getBytes());
int index = fileName.lastIndexOf(".");
String contentId = (index == -1) ? fileName : fileName.substring(0, index);
part.setContentId(contentId.getBytes());
part.setData(StreamUtil.readFully(PartAuthority.getAttachmentStream(context, attachment.getUri())));
body.addPart(part);
size += getPartSize(part);
} catch (IOException e) {
Log.w(TAG, e);
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(body), out);
PduPart smilPart = new PduPart();
smilPart.setContentId("smil".getBytes());
smilPart.setContentLocation("smil.xml".getBytes());
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
smilPart.setData(out.toByteArray());
body.addPart(0, smilPart);
req.setBody(body);
req.setMessageSize(size);
req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
req.setExpiry(7 * 24 * 60 * 60);
try {
req.setPriority(PduHeaders.PRIORITY_NORMAL);
req.setDeliveryReport(PduHeaders.VALUE_NO);
req.setReadReport(PduHeaders.VALUE_NO);
} catch (InvalidHeaderValueException e) {}
return req;
}
private long getPartSize(PduPart part) {
return part.getName().length + part.getContentLocation().length +
part.getContentType().length + part.getData().length +
part.getContentId().length;
}
private void notifyMediaMessageDeliveryFailed(Context context, long messageId) {
long threadId = SignalDatabase.messages().getThreadIdForMessage(messageId);
Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId);
if (recipient != null) {
ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, ConversationId.forConversation(threadId));
}
}
private String getMyNumber(Context context) throws UndeliverableMessageException {
try {
return Utils.getMyPhoneNumber(context);
} catch (SecurityException e) {
throw new UndeliverableMessageException(e);
}
}
public static class Factory implements Job.Factory<MmsSendJob> {
@Override
public @NonNull MmsSendJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
JsonJobData data = JsonJobData.deserialize(serializedData);
return new MmsSendJob(parameters, data.getLong(KEY_MESSAGE_ID));
}
}
}

View File

@@ -1,259 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.util.ArrayList;
public class SmsSendJob extends SendJob {
public static final String KEY = "SmsSendJob";
private static final String TAG = Log.tag(SmsSendJob.class);
private static final int MAX_ATTEMPTS = 15;
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_RUN_ATTEMPT = "run_attempt";
private final long messageId;
private final int runAttempt;
public SmsSendJob(long messageId, @NonNull Recipient destination) {
this(messageId, destination, 0);
}
public SmsSendJob(long messageId, @NonNull Recipient destination, int runAttempt) {
this(constructParameters(destination), messageId, runAttempt);
}
private SmsSendJob(@NonNull Job.Parameters parameters, long messageId, int runAttempt) {
super(parameters);
this.messageId = messageId;
this.runAttempt = runAttempt;
}
@Override
public @Nullable byte[] serialize() {
return new JsonJobData.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putInt(KEY_RUN_ATTEMPT, runAttempt)
.serialize();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
SignalDatabase.messages().markAsSending(messageId);
}
@Override
public void onSend() throws NoSuchMessageException, TooManyRetriesException, UndeliverableMessageException {
if (runAttempt >= MAX_ATTEMPTS) {
warn(TAG, "Hit the retry limit. Failing.");
throw new TooManyRetriesException();
}
MessageTable database = SignalDatabase.messages();
MessageRecord record = database.getMessageRecord(messageId);
if (!record.isPending() && !record.isFailed()) {
warn(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
}
if (!record.getToRecipient().hasSmsAddress()) {
throw new UndeliverableMessageException("Recipient didn't have an SMS address! " + record.getToRecipient().getId());
}
try {
log(TAG, String.valueOf(record.getDateSent()), "Sending message: " + messageId + " (attempt " + runAttempt + ")");
deliver(record);
log(TAG, String.valueOf(record.getDateSent()), "Sent message: " + messageId);
} catch (UndeliverableMessageException ude) {
warn(TAG, ude);
SignalDatabase.messages().markAsSentFailed(record.getId());
ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getToRecipient(), ConversationId.fromMessageRecord(record));
}
}
@Override
public boolean onShouldRetry(@NonNull Exception throwable) {
return false;
}
@Override
public void onFailure() {
warn(TAG, "onFailure() messageId: " + messageId);
long threadId = SignalDatabase.messages().getThreadIdForMessage(messageId);
Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId);
SignalDatabase.messages().markAsSentFailed(messageId);
if (threadId != -1 && recipient != null) {
ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, ConversationId.forConversation(threadId));
} else {
Log.w(TAG, "Could not find message! threadId: " + threadId + ", recipient: " + (recipient != null ? recipient.getId().toString() : "null"));
}
}
private void deliver(MessageRecord message)
throws UndeliverableMessageException
{
if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
throw new UndeliverableMessageException("Trying to send a secure SMS?");
}
String recipient = message.getToRecipient().requireSmsAddress();
// See issue #1516 for bug report, and discussion on commits related to #4833 for problems
// related to the original fix to #1516. This still may not be a correct fix if networks allow
// SMS/MMS sending to alphanumeric recipients other than email addresses, but should also
// help to fix issue #3099.
if (!NumberUtil.isValidEmail(recipient)) {
recipient = PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(recipient));
}
if (!NumberUtil.isValidSmsOrEmail(recipient)) {
throw new UndeliverableMessageException("Not a valid SMS destination! " + recipient);
}
SmsManager smsManager = getSmsManagerFor(message.getSubscriptionId());
ArrayList<String> messages = smsManager.divideMessage(message.getBody());
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
// NOTE 11/04/14 -- There's apparently a bug where for some unknown recipients
// and messages, this will throw an NPE. We have no idea why, so we're just
// catching it and marking the message as a failure. That way at least it doesn't
// repeatedly crash every time you start the app.
try {
smsManager.sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
} catch (NullPointerException | IllegalArgumentException npe) {
warn(TAG, npe);
log(TAG, String.valueOf(message.getDateSent()), "Recipient: " + recipient);
log(TAG, String.valueOf(message.getDateSent()), "Message Parts: " + messages.size());
try {
for (int i=0;i<messages.size();i++) {
smsManager.sendTextMessage(recipient, null, messages.get(i), sentIntents.get(i),
deliveredIntents == null ? null : deliveredIntents.get(i));
}
} catch (NullPointerException | IllegalArgumentException npe2) {
warn(TAG, npe);
throw new UndeliverableMessageException(npe2);
}
} catch (SecurityException se) {
warn(TAG, se);
throw new UndeliverableMessageException(se);
}
}
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type, ArrayList<String> messages)
{
ArrayList<PendingIntent> sentIntents = new ArrayList<>(messages.size());
boolean isMultipart = messages.size() > 1;
for (String ignored : messages) {
sentIntents.add(PendingIntent.getBroadcast(context, 0,
constructSentIntent(context, messageId, type, isMultipart),
PendingIntentFlags.mutable()));
}
return sentIntents;
}
private ArrayList<PendingIntent> constructDeliveredIntents(long messageId, long type, ArrayList<String> messages) {
if (!SignalStore.settings().isSmsDeliveryReportsEnabled()) {
return null;
}
ArrayList<PendingIntent> deliveredIntents = new ArrayList<>(messages.size());
boolean isMultipart = messages.size() > 1;
for (String ignored : messages) {
deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
constructDeliveredIntent(context, messageId, type, isMultipart),
PendingIntentFlags.mutable()));
}
return deliveredIntents;
}
private Intent constructSentIntent(Context context, long messageId, long type, boolean isMultipart) {
Intent pending = new Intent(SmsDeliveryListener.SENT_SMS_ACTION,
Uri.parse("custom://" + messageId + System.currentTimeMillis()),
context, SmsDeliveryListener.class);
pending.putExtra("type", type);
pending.putExtra("message_id", messageId);
pending.putExtra("run_attempt", Math.max(runAttempt, getRunAttempt()));
pending.putExtra("is_multipart", isMultipart);
return pending;
}
private Intent constructDeliveredIntent(Context context, long messageId, long type, boolean isMultipart) {
Intent pending = new Intent(SmsDeliveryListener.DELIVERED_SMS_ACTION,
Uri.parse("custom://" + messageId + System.currentTimeMillis()),
context, SmsDeliveryListener.class);
pending.putExtra("type", type);
pending.putExtra("message_id", messageId);
pending.putExtra("is_multipart", isMultipart);
return pending;
}
private SmsManager getSmsManagerFor(int subscriptionId) {
if (Build.VERSION.SDK_INT >= 22 && subscriptionId != -1) {
return SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
return SmsManager.getDefault();
}
}
private static Job.Parameters constructParameters(@NonNull Recipient destination) {
return new Job.Parameters.Builder()
.setMaxAttempts(MAX_ATTEMPTS)
.setQueue(destination.getId().toQueueKey() + "::SMS")
.addConstraint(NetworkOrCellServiceConstraint.KEY)
.build();
}
private static class TooManyRetriesException extends Exception { }
public static class Factory implements Job.Factory<SmsSendJob> {
@Override
public @NonNull SmsSendJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
JsonJobData data = JsonJobData.deserialize(serializedData);
return new SmsSendJob(parameters, data.getLong(KEY_MESSAGE_ID), data.getInt(KEY_RUN_ATTEMPT));
}
}
}

View File

@@ -1,141 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.app.Activity;
import android.telephony.SmsManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
public class SmsSentJob extends BaseJob {
public static final String KEY = "SmsSentJob";
private static final String TAG = Log.tag(SmsSentJob.class);
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_IS_MULTIPART = "is_multipart";
private static final String KEY_ACTION = "action";
private static final String KEY_RESULT = "result";
private static final String KEY_RUN_ATTEMPT = "run_attempt";
private final long messageId;
private final boolean isMultipart;
private final String action;
private final int result;
private final int runAttempt;
public SmsSentJob(long messageId, boolean isMultipart, String action, int result, int runAttempt) {
this(new Job.Parameters.Builder().build(),
messageId,
isMultipart,
action,
result,
runAttempt);
}
private SmsSentJob(@NonNull Job.Parameters parameters, long messageId, boolean isMultipart, String action, int result, int runAttempt) {
super(parameters);
this.messageId = messageId;
this.isMultipart = isMultipart;
this.action = action;
this.result = result;
this.runAttempt = runAttempt;
}
@Override
public @Nullable byte[] serialize() {
return new JsonJobData.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putBoolean(KEY_IS_MULTIPART, isMultipart)
.putString(KEY_ACTION, action)
.putInt(KEY_RESULT, result)
.putInt(KEY_RUN_ATTEMPT, runAttempt)
.serialize();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() {
Log.i(TAG, "Got SMS callback: " + action + " , " + result);
switch (action) {
case SmsDeliveryListener.SENT_SMS_ACTION:
handleSentResult(messageId, result);
break;
case SmsDeliveryListener.DELIVERED_SMS_ACTION:
handleDeliveredResult(messageId, result);
break;
}
}
@Override
public boolean onShouldRetry(@NonNull Exception throwable) {
return false;
}
@Override
public void onFailure() {
}
private void handleDeliveredResult(long messageId, int result) {
SignalDatabase.messages().markSmsStatus(messageId, result);
}
private void handleSentResult(long messageId, int result) {
try {
MessageTable database = SignalDatabase.messages();
MessageRecord record = database.getMessageRecord(messageId);
switch (result) {
case Activity.RESULT_OK:
database.markAsSent(messageId, false);
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
case SmsManager.RESULT_ERROR_RADIO_OFF:
if (isMultipart) {
Log.w(TAG, "Service connectivity problem, but not retrying due to multipart");
database.markAsSentFailed(messageId);
ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getToRecipient(), ConversationId.forConversation(record.getThreadId()));
} else {
Log.w(TAG, "Service connectivity problem, requeuing...");
ApplicationDependencies.getJobManager().add(new SmsSendJob(messageId, record.getToRecipient(), runAttempt + 1));
}
break;
default:
database.markAsSentFailed(messageId);
ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getToRecipient(), ConversationId.forConversation(record.getThreadId()));
}
} catch (NoSuchMessageException e) {
Log.w(TAG, e);
}
}
public static final class Factory implements Job.Factory<SmsSentJob> {
@Override
public @NonNull SmsSentJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
JsonJobData data = JsonJobData.deserialize(serializedData);
return new SmsSentJob(parameters,
data.getLong(KEY_MESSAGE_ID),
data.getBooleanOrDefault(KEY_IS_MULTIPART, true),
data.getString(KEY_ACTION),
data.getInt(KEY_RESULT),
data.getInt(KEY_RUN_ATTEMPT));
}
}
}

View File

@@ -235,10 +235,6 @@ public final class MiscellaneousValues extends SignalStoreValues {
putBoolean(PNI_INITIALIZED_DEVICES, value);
}
public @NonNull SmsExportPhase getSmsExportPhase() {
return SmsExportPhase.getCurrentPhase();
}
public void setHasLinkedDevices(boolean value) {
putBoolean(HAS_LINKED_DEVICES, value);
}

View File

@@ -65,7 +65,6 @@ public final class SettingsValues extends SignalStoreValues {
public static final String CALL_RINGTONE = "settings.call.ringtone";
public static final String CALL_VIBRATE_ENABLED = "settings.call.vibrate.enabled";
public static final String NOTIFY_WHEN_CONTACT_JOINS_SIGNAL = "settings.notify.when.contact.joins.signal";
private static final String DEFAULT_SMS = "settings.default_sms";
private static final String UNIVERSAL_EXPIRE_TIMER = "settings.universal.expire.timer";
private static final String SENT_MEDIA_QUALITY = "settings.sentMediaQuality";
private static final String CENSORSHIP_CIRCUMVENTION_ENABLED = "settings.censorshipCircumventionEnabled";
@@ -411,26 +410,6 @@ public final class SettingsValues extends SignalStoreValues {
putBoolean(NOTIFY_WHEN_CONTACT_JOINS_SIGNAL, notifyWhenContactJoinsSignal);
}
/**
* We need to keep track of when the default status changes so we can sync to storage service.
* So call this when you think it might have changed, but *don't* rely on it for knowing if we
* *are* the default SMS. For that, continue to use
* {@link org.thoughtcrime.securesms.util.Util#isDefaultSmsProvider(Context)}.
*/
public void setDefaultSms(boolean value) {
boolean lastKnown = getBoolean(DEFAULT_SMS, false);
if (value != lastKnown && SignalStore.registrationValues().isRegistrationComplete()) {
Log.i(TAG, "Default SMS state changed! Scheduling a storage sync.");
putBoolean(DEFAULT_SMS, value);
SignalExecutors.BOUNDED.execute(() -> {
SignalDatabase.recipients().markNeedsSync(Recipient.self().getId());
StorageSyncHelper.scheduleSyncForDataChange();
});
}
}
public void setUniversalExpireTimer(int seconds) {
putInteger(UNIVERSAL_EXPIRE_TIMER, seconds);
}

View File

@@ -1,24 +0,0 @@
package org.thoughtcrime.securesms.keyvalue
enum class SmsExportPhase(val duration: Long) {
PHASE_3(0);
fun allowSmsFeatures(): Boolean {
return false
}
fun isSmsSupported(): Boolean {
return false
}
fun isBlockingUi(): Boolean {
return true
}
companion object {
@JvmStatic
fun getCurrentPhase(): SmsExportPhase {
return PHASE_3
}
}
}

View File

@@ -107,7 +107,7 @@ internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.ConfirmKbsPinFragment__pin_creation_failed)
.setMessage(R.string.ConfirmKbsPinFragment__your_pin_was_not_saved)
.setCancelable(false)
.setPositiveButton(R.string.ok) { d: DialogInterface, w: Int ->
.setPositiveButton(android.R.string.ok) { d: DialogInterface, w: Int ->
d.dismiss()
markMegaphoneSeenIfNecessary()
requireActivity().setResult(Activity.RESULT_CANCELED)

View File

@@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.logsubmit
import android.content.Context
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
/**
* Prints off the current SMS settings
*/
class LogSectionSMS : LogSection {
override fun getTitle(): String = "SMS"
override fun getContent(context: Context): CharSequence {
val isDefaultSMS = Util.isDefaultSmsProvider(context)
val settings = SignalStore.settings()
val output = StringBuilder()
output.append("Default SMS : ${isDefaultSMS}\n")
output.append("SMS delivery reports : ${settings.isSmsDeliveryReportsEnabled}\n")
output.append("WiFi SMS : ${settings.isWifiCallingCompatibilityModeEnabled}\n")
return output
}
}

View File

@@ -86,7 +86,6 @@ public class SubmitDebugLogRepository {
add(new LogSectionNotificationProfiles());
add(new LogSectionExoPlayerPool());
add(new LogSectionKeyPreferences());
add(new LogSectionSMS());
add(new LogSectionStories());
add(new LogSectionBadges());
add(new LogSectionPermissions());

View File

@@ -79,7 +79,7 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
@Override
public void onCameraError() {
Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.default_error_msg, Toast.LENGTH_SHORT).show();
finish();
}

View File

@@ -80,7 +80,6 @@ class MediaSelectionRepository(context: Context) {
stateMap: Map<Uri, Any>,
quality: SentMediaQuality,
message: CharSequence?,
isSms: Boolean,
isViewOnce: Boolean,
singleContact: ContactSearchKey.RecipientSearchKey?,
contacts: List<ContactSearchKey.RecipientSearchKey>,
@@ -89,10 +88,6 @@ class MediaSelectionRepository(context: Context) {
sendType: MessageSendType,
scheduledTime: Long = -1
): Maybe<MediaSendActivityResult> {
if (isSms && contacts.isNotEmpty()) {
throw IllegalStateException("Provided recipients to send to, but this is SMS!")
}
if (selectedMedia.isEmpty()) {
throw IllegalStateException("No selected media!")
}
@@ -121,8 +116,8 @@ class MediaSelectionRepository(context: Context) {
StoryType.NONE
}
if (isSms || MessageSender.isLocalSelfSend(context, singleRecipient, SendType.SIGNAL)) {
Log.i(TAG, "SMS or local self-send. Skipping pre-upload.")
if (MessageSender.isLocalSelfSend(context, singleRecipient, SendType.SIGNAL)) {
Log.i(TAG, "Local self-send. Skipping pre-upload.")
emitter.onSuccess(
MediaSendActivityResult(
recipientId = singleRecipient!!.id,
@@ -207,7 +202,7 @@ class MediaSelectionRepository(context: Context) {
)
)
} else {
Log.w(TAG, "Got empty upload results! isSms: $isSms, updatedMedia.size(): ${updatedMedia.size}, isViewOnce: $isViewOnce, target: $singleContact")
Log.w(TAG, "Got empty upload results! updatedMedia.size(): ${updatedMedia.size}, isViewOnce: $isViewOnce, target: $singleContact")
emitter.onSuccess(
MediaSendActivityResult(
recipientId = singleRecipient!!.id,
@@ -249,8 +244,8 @@ class MediaSelectionRepository(context: Context) {
uploadRepository.deleteAbandonedAttachments()
}
fun isLocalSelfSend(recipient: Recipient?, isSms: Boolean): Boolean {
return MessageSender.isLocalSelfSend(context, recipient, if (isSms) MessageSender.SendType.SMS else MessageSender.SendType.SIGNAL)
fun isLocalSelfSend(recipient: Recipient?): Boolean {
return MessageSender.isLocalSelfSend(context, recipient, SendType.SIGNAL)
}
@WorkerThread

View File

@@ -4,7 +4,6 @@ import android.net.Uri
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendConstants
import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData
import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.SentMediaQuality
@@ -37,11 +36,7 @@ data class MediaSelectionState(
val transcodingPreset: TranscodingPreset = MediaConstraints.getPushMediaConstraints(SentMediaQuality.fromCode(quality.code)).videoTranscodingSettings
val maxSelection = if (sendType.usesSmsTransport) {
MediaSendConstants.MAX_SMS
} else {
FeatureFlags.maxAttachmentCount()
}
val maxSelection = FeatureFlags.maxAttachmentCount()
val canSend = !isSent && selectedMedia.isNotEmpty()

View File

@@ -108,7 +108,7 @@ class MediaSelectionViewModel(
store.update {
it.copy(
isMeteredConnection = metered,
isPreUploadEnabled = shouldPreUpload(metered, it.sendType.usesSmsTransport, it.recipient)
isPreUploadEnabled = shouldPreUpload(metered, it.recipient)
)
}
}
@@ -121,7 +121,7 @@ class MediaSelectionViewModel(
store.update(Recipient.live(recipientSearchKey.recipientId).liveData) { r, s ->
s.copy(
recipient = r,
isPreUploadEnabled = shouldPreUpload(s.isMeteredConnection, s.sendType.usesSmsTransport, r)
isPreUploadEnabled = shouldPreUpload(s.isMeteredConnection, r)
)
}
}
@@ -320,11 +320,7 @@ class MediaSelectionViewModel(
}
fun getMediaConstraints(): MediaConstraints {
return if (store.state.sendType.usesSmsTransport) {
MediaConstraints.getMmsMediaConstraints(store.state.sendType.simSubscriptionId ?: -1)
} else {
MediaConstraints.getPushMediaConstraints()
}
return MediaConstraints.getPushMediaConstraints()
}
fun setSentMediaQuality(sentMediaQuality: SentMediaQuality) {
@@ -396,7 +392,6 @@ class MediaSelectionViewModel(
stateMap = store.state.editorStateMap,
quality = store.state.quality,
message = store.state.message,
isSms = store.state.sendType.usesSmsTransport,
isViewOnce = isViewOnceEnabled(),
singleContact = destination.getRecipientSearchKey(),
contacts = selectedContacts.ifEmpty { destination.getRecipientSearchKeyList() },
@@ -409,8 +404,7 @@ class MediaSelectionViewModel(
}
private fun isViewOnceEnabled(): Boolean {
return !store.state.sendType.usesSmsTransport &&
store.state.selectedMedia.size == 1 &&
return store.state.selectedMedia.size == 1 &&
store.state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE
}
@@ -432,8 +426,8 @@ class MediaSelectionViewModel(
repository.uploadRepository.cancelUpload(media)
}
private fun shouldPreUpload(metered: Boolean, isSms: Boolean, recipient: Recipient?): Boolean {
return !metered && !isSms && !repository.isLocalSelfSend(recipient, isSms)
private fun shouldPreUpload(metered: Boolean, recipient: Recipient?): Boolean {
return !metered && !repository.isLocalSelfSend(recipient)
}
fun onSaveState(outState: Bundle) {

View File

@@ -205,7 +205,6 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
if (sharedViewModel.isContactSelectionRequired) {
val args = MultiselectForwardFragmentArgs(
false,
title = R.string.MediaReviewFragment__send_to,
storySendRequirements = sharedViewModel.getStorySendRequirements(),
isSearchEnabled = !sharedViewModel.isStory(),

View File

@@ -146,7 +146,6 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
StoriesMultiselectForwardActivity.Args(
MultiselectForwardFragmentArgs(
title = R.string.MediaReviewFragment__send_to,
canSendToNonPush = false,
storySendRequirements = Stories.MediaTransform.SendRequirements.VALID_DURATION,
isSearchEnabled = false
),

View File

@@ -1,19 +0,0 @@
package org.thoughtcrime.securesms.mms;
public class ApnUnavailableException extends Exception {
public ApnUnavailableException() {
}
public ApnUnavailableException(String detailMessage) {
super(detailMessage);
}
public ApnUnavailableException(Throwable throwable) {
super(throwable);
}
public ApnUnavailableException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}

View File

@@ -1,101 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.RetrieveConf;
import com.google.android.mms.pdu_alt.SendConf;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.IOException;
public class CompatMmsConnection implements OutgoingMmsConnection, IncomingMmsConnection {
private static final String TAG = Log.tag(CompatMmsConnection.class);
private Context context;
public CompatMmsConnection(Context context) {
this.context = context;
}
@Nullable
@Override
public SendConf send(@NonNull byte[] pduBytes, int subscriptionId)
throws UndeliverableMessageException
{
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
try {
Log.i(TAG, "Sending via Lollipop API");
return new OutgoingLollipopMmsConnection(context).send(pduBytes, subscriptionId);
} catch (UndeliverableMessageException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Falling back to legacy connection...");
}
if (subscriptionId == -1) {
Log.i(TAG, "Sending via legacy connection");
try {
SendConf result = new OutgoingLegacyMmsConnection(context).send(pduBytes, subscriptionId);
if (result != null && result.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
return result;
} else {
Log.w(TAG, "Got bad legacy response: " + (result != null ? result.getResponseStatus() : null));
}
} catch (UndeliverableMessageException | ApnUnavailableException e) {
Log.w(TAG, e);
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && VERSION.SDK_INT < VERSION_CODES.LOLLIPOP_MR1) {
Log.i(TAG, "Falling back to sending via Lollipop API");
return new OutgoingLollipopMmsConnection(context).send(pduBytes, subscriptionId);
}
throw new UndeliverableMessageException("Both lollipop and legacy connections failed...");
}
@Nullable
@Override
public RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId,
int subscriptionId)
throws MmsException, MmsRadioException, ApnUnavailableException, IOException
{
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
Log.i(TAG, "Receiving via Lollipop API");
try {
return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
} catch (MmsException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Falling back to receiving via legacy connection");
}
if (VERSION.SDK_INT < 22 || subscriptionId == -1) {
Log.i(TAG, "Receiving via legacy API");
try {
return new IncomingLegacyMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
} catch (MmsRadioException | ApnUnavailableException | IOException e) {
Log.w(TAG, e);
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && VERSION.SDK_INT < VERSION_CODES.LOLLIPOP_MR1) {
Log.i(TAG, "Falling back to receiving via Lollipop API");
return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
}
throw new IOException("Both lollipop and fallback APIs failed...");
}
}

View File

@@ -1,157 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.pdu_alt.NotifyRespInd;
import com.google.android.mms.pdu_alt.PduComposer;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.RetrieveConf;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGetHC4;
import org.apache.http.client.methods.HttpUriRequest;
import org.signal.core.util.logging.Log;
import java.io.IOException;
import java.util.Arrays;
@SuppressWarnings("deprecation")
public class IncomingLegacyMmsConnection extends LegacyMmsConnection implements IncomingMmsConnection {
private static final String TAG = Log.tag(IncomingLegacyMmsConnection.class);
public IncomingLegacyMmsConnection(Context context) throws ApnUnavailableException {
super(context);
}
private HttpUriRequest constructRequest(Apn contentApn, boolean useProxy) throws IOException {
HttpGetHC4 request;
try {
request = new HttpGetHC4(contentApn.getMmsc());
} catch (IllegalArgumentException e) {
// #7339
throw new IOException(e);
}
for (Header header : getBaseHeaders()) {
request.addHeader(header);
}
if (useProxy) {
HttpHost proxy = new HttpHost(contentApn.getProxy(), contentApn.getPort());
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
}
return request;
}
@Override
public @Nullable RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId, int subscriptionId)
throws MmsRadioException, ApnUnavailableException, IOException
{
MmsRadio radio = MmsRadio.getInstance(context);
Apn contentApn = new Apn(contentLocation, apn.getProxy(), Integer.toString(apn.getPort()), apn.getUsername(), apn.getPassword());
if (isDirectConnect()) {
Log.i(TAG, "Connecting directly...");
try {
return retrieve(contentApn, transactionId, false, false);
} catch (IOException | ApnUnavailableException e) {
Log.w(TAG, e);
}
}
Log.i(TAG, "Changing radio to MMS mode..");
radio.connect();
try {
Log.i(TAG, "Downloading in MMS mode with proxy...");
try {
return retrieve(contentApn, transactionId, true, true);
} catch (IOException | ApnUnavailableException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Downloading in MMS mode without proxy...");
return retrieve(contentApn, transactionId, true, false);
} finally {
radio.disconnect();
}
}
public RetrieveConf retrieve(Apn contentApn, byte[] transactionId, boolean usingMmsRadio, boolean useProxyIfAvailable)
throws IOException, ApnUnavailableException
{
byte[] pdu = null;
final boolean useProxy = useProxyIfAvailable && contentApn.hasProxy();
final String targetHost = useProxy
? contentApn.getProxy()
: Uri.parse(contentApn.getMmsc()).getHost();
if (checkRouteToHost(context, targetHost, usingMmsRadio)) {
Log.i(TAG, "got successful route to host " + targetHost);
pdu = execute(constructRequest(contentApn, useProxy));
}
if (pdu == null) {
throw new IOException("Connection manager could not obtain route to host.");
}
RetrieveConf retrieved = (RetrieveConf)new PduParser(pdu).parse();
if (retrieved == null) {
Log.w(TAG, "Couldn't parse PDU, byte response: " + Arrays.toString(pdu));
Log.w(TAG, "Couldn't parse PDU, ASCII: " + new String(pdu));
throw new IOException("Bad retrieved PDU");
}
sendRetrievedAcknowledgement(transactionId, usingMmsRadio, useProxy);
return retrieved;
}
private void sendRetrievedAcknowledgement(byte[] transactionId,
boolean usingRadio,
boolean useProxy)
throws ApnUnavailableException
{
try {
NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION,
transactionId,
PduHeaders.STATUS_RETRIEVED);
OutgoingLegacyMmsConnection connection = new OutgoingLegacyMmsConnection(context);
connection.sendNotificationReceived(new PduComposer(context, notifyResponse).make(), usingRadio, useProxy);
} catch (InvalidHeaderValueException | IOException e) {
Log.w(TAG, e);
}
}
}

View File

@@ -1,156 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.pdu_alt.NotifyRespInd;
import com.google.android.mms.pdu_alt.PduComposer;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.RetrieveConf;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.providers.MmsBodyProvider;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
public class IncomingLollipopMmsConnection extends LollipopMmsConnection implements IncomingMmsConnection {
public static final String ACTION = IncomingLollipopMmsConnection.class.getCanonicalName() + "MMS_DOWNLOADED_ACTION";
private static final String TAG = Log.tag(IncomingLollipopMmsConnection.class);
public IncomingLollipopMmsConnection(Context context) {
super(context, ACTION);
}
@Override
public synchronized void onResult(Context context, Intent intent) {
if (VERSION.SDK_INT >= 22) {
Log.i(TAG, "HTTP status: " + intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, -1));
}
Log.i(TAG, "code: " + getResultCode() + ", result string: " + getResultData());
}
@Override
public synchronized @Nullable RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId,
int subscriptionId) throws MmsException
{
beginTransaction();
try {
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
final String transactionIdString = Util.toIsoString(transactionId);
Log.i(TAG, String.format(Locale.ENGLISH, "Downloading subscriptionId=%s multimedia from '%s' [transactionId='%s'] to '%s'",
subscriptionId,
contentLocation,
transactionIdString,
pointer.getUri()));
SmsManager smsManager;
if (VERSION.SDK_INT >= 22 && subscriptionId != -1) {
smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
smsManager = SmsManager.getDefault();
}
final Bundle configOverrides = smsManager.getCarrierConfigValues();
if (configOverrides.getBoolean(SmsManager.MMS_CONFIG_APPEND_TRANSACTION_ID)) {
if (!contentLocation.contains(transactionIdString)) {
Log.i(TAG, "Appending transactionId to contentLocation at the direction of CarrierConfigValues. New location: " + contentLocation);
contentLocation += transactionIdString;
} else {
Log.i(TAG, "Skipping 'append transaction id' as contentLocation already contains it");
}
}
if (TextUtils.isEmpty(configOverrides.getString(SmsManager.MMS_CONFIG_USER_AGENT))) {
configOverrides.remove(SmsManager.MMS_CONFIG_USER_AGENT);
}
if (TextUtils.isEmpty(configOverrides.getString(SmsManager.MMS_CONFIG_UA_PROF_URL))) {
configOverrides.remove(SmsManager.MMS_CONFIG_UA_PROF_URL);
}
smsManager.downloadMultimediaMessage(getContext(),
contentLocation,
pointer.getUri(),
configOverrides,
getPendingIntent());
waitForResult();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamUtil.copy(pointer.getInputStream(), baos);
pointer.close();
Log.i(TAG, baos.size() + "-byte response: ");// + Hex.dump(baos.toByteArray()));
Bundle configValues = smsManager.getCarrierConfigValues();
boolean parseContentDisposition = configValues.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION);
RetrieveConf retrieved;
try {
retrieved = (RetrieveConf) new PduParser(baos.toByteArray(), parseContentDisposition).parse();
} catch (AssertionError | NullPointerException e) {
Log.w(TAG, "Badly formatted MMS message caused the parser to fail.", e);
throw new MmsException(e);
}
if (retrieved == null) return null;
sendRetrievedAcknowledgement(transactionId, retrieved.getMmsVersion(), subscriptionId);
return retrieved;
} catch (IOException | TimeoutException e) {
Log.w(TAG, e);
throw new MmsException(e);
} finally {
endTransaction();
}
}
private void sendRetrievedAcknowledgement(byte[] transactionId, int mmsVersion, int subscriptionId) {
try {
NotifyRespInd retrieveResponse = new NotifyRespInd(mmsVersion, transactionId, PduHeaders.STATUS_RETRIEVED);
new OutgoingLollipopMmsConnection(getContext()).send(new PduComposer(getContext(), retrieveResponse).make(), subscriptionId);
} catch (UndeliverableMessageException e) {
Log.w(TAG, e);
} catch (InvalidHeaderValueException e) {
Log.w(TAG, e);
}
}
}

View File

@@ -1,13 +0,0 @@
package org.thoughtcrime.securesms.mms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.RetrieveConf;
import java.io.IOException;
public interface IncomingMmsConnection {
@Nullable
RetrieveConf retrieve(@NonNull String contentLocation, byte[] transactionId, int subscriptionId) throws MmsException, MmsRadioException, ApnUnavailableException, IOException;
}

View File

@@ -1,313 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import org.apache.http.Header;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.NoConnectionReuseStrategyHC4;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.ApnDatabase;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@SuppressWarnings("deprecation")
public abstract class LegacyMmsConnection {
public static final String USER_AGENT = "Android-Mms/2.0";
private static final String TAG = Log.tag(LegacyMmsConnection.class);
protected final Context context;
protected final Apn apn;
protected LegacyMmsConnection(Context context) throws ApnUnavailableException {
this.context = context;
this.apn = getApn(context);
}
public static Apn getApn(Context context) throws ApnUnavailableException {
try {
Optional<Apn> params = ApnDatabase.getInstance(context)
.getMmsConnectionParameters(TelephonyUtil.getMccMnc(context),
TelephonyUtil.getApn(context));
if (!params.isPresent()) {
throw new ApnUnavailableException("No parameters available from ApnDefaults.");
}
return params.get();
} catch (IOException ioe) {
throw new ApnUnavailableException("ApnDatabase threw an IOException", ioe);
}
}
protected boolean isDirectConnect() {
// We think Sprint supports direct connection over wifi/data, but not Verizon
Set<String> sprintMccMncs = new HashSet<String>() {{
add("312530");
add("311880");
add("311870");
add("311490");
add("310120");
add("316010");
add("312190");
}};
return ServiceUtil.getTelephonyManager(context).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA &&
sprintMccMncs.contains(TelephonyUtil.getMccMnc(context));
}
@SuppressWarnings("TryWithIdenticalCatches")
protected static boolean checkRouteToHost(Context context, String host, boolean usingMmsRadio)
throws IOException
{
InetAddress inetAddress = InetAddress.getByName(host);
if (!usingMmsRadio) {
if (inetAddress.isSiteLocalAddress()) {
throw new IOException("RFC1918 address in non-MMS radio situation!");
}
Log.w(TAG, "returning vacuous success since MMS radio is not in use");
return true;
}
if (inetAddress == null) {
throw new IOException("Unable to lookup host: InetAddress.getByName() returned null.");
}
byte[] ipAddressBytes = inetAddress.getAddress();
if (ipAddressBytes == null) {
Log.w(TAG, "resolved IP address bytes are null, returning true to attempt a connection anyway.");
return true;
}
Log.i(TAG, "Checking route to address: " + host + ", " + inetAddress.getHostAddress());
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
final Method requestRouteMethod = manager.getClass().getMethod("requestRouteToHostAddress", Integer.TYPE, InetAddress.class);
final boolean routeToHostObtained = (Boolean) requestRouteMethod.invoke(manager, MmsRadio.TYPE_MOBILE_MMS, inetAddress);
Log.i(TAG, "requestRouteToHostAddress(" + inetAddress + ") -> " + routeToHostObtained);
return routeToHostObtained;
} catch (NoSuchMethodException nsme) {
Log.w(TAG, nsme);
} catch (IllegalAccessException iae) {
Log.w(TAG, iae);
} catch (InvocationTargetException ite) {
Log.w(TAG, ite);
}
return false;
}
protected static byte[] parseResponse(InputStream is) throws IOException {
InputStream in = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamUtil.copy(in, baos);
Log.i(TAG, "Received full server response, " + baos.size() + " bytes");
return baos.toByteArray();
}
protected CloseableHttpClient constructHttpClient() throws IOException {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(20 * 1000)
.setConnectionRequestTimeout(20 * 1000)
.setSocketTimeout(20 * 1000)
.setMaxRedirects(20)
.build();
URL mmsc = new URL(apn.getMmsc());
CredentialsProvider credsProvider = new BasicCredentialsProvider();
if (apn.hasAuthentication()) {
credsProvider.setCredentials(new AuthScope(mmsc.getHost(), mmsc.getPort() > -1 ? mmsc.getPort() : mmsc.getDefaultPort()),
new UsernamePasswordCredentials(apn.getUsername(), apn.getPassword()));
}
return HttpClients.custom()
.setConnectionReuseStrategy(new NoConnectionReuseStrategyHC4())
.setRedirectStrategy(new LaxRedirectStrategy())
.setUserAgent(TextSecurePreferences.getMmsUserAgent(context, USER_AGENT))
.setConnectionManager(new BasicHttpClientConnectionManager())
.setDefaultRequestConfig(config)
.setDefaultCredentialsProvider(credsProvider)
.build();
}
protected byte[] execute(HttpUriRequest request) throws IOException {
Log.i(TAG, "connecting to " + apn.getMmsc());
CloseableHttpClient client = null;
CloseableHttpResponse response = null;
try {
client = constructHttpClient();
response = client.execute(request);
Log.i(TAG, "* response code: " + response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 200) {
return parseResponse(response.getEntity().getContent());
}
} catch (NullPointerException npe) {
// TODO determine root cause
// see: https://github.com/signalapp/Signal-Android/issues/4379
throw new IOException(npe);
} finally {
if (response != null) response.close();
if (client != null) client.close();
}
throw new IOException("unhandled response code");
}
protected List<Header> getBaseHeaders() {
final String number = getLine1Number(context);
return new LinkedList<Header>() {{
add(new BasicHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"));
add(new BasicHeader("x-wap-profile", "http://www.google.com/oha/rdf/ua-profile-kila.xml"));
add(new BasicHeader("Content-Type", "application/vnd.wap.mms-message"));
add(new BasicHeader("x-carrier-magic", "http://magic.google.com"));
if (!TextUtils.isEmpty(number)) {
add(new BasicHeader("x-up-calling-line-id", number));
add(new BasicHeader("X-MDN", number));
}
}};
}
@SuppressLint("HardwareIds")
private static String getLine1Number(@NonNull Context context) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
return TelephonyUtil.getManager(context).getLine1Number();
} else {
return "";
}
}
public static class Apn {
public static Apn EMPTY = new Apn("", "", "", "", "");
private final String mmsc;
private final String proxy;
private final String port;
private final String username;
private final String password;
public Apn(String mmsc, String proxy, String port, String username, String password) {
this.mmsc = mmsc;
this.proxy = proxy;
this.port = port;
this.username = username;
this.password = password;
}
public Apn(Apn customApn, Apn defaultApn,
boolean useCustomMmsc,
boolean useCustomProxy,
boolean useCustomProxyPort,
boolean useCustomUsername,
boolean useCustomPassword)
{
this.mmsc = useCustomMmsc ? customApn.mmsc : defaultApn.mmsc;
this.proxy = useCustomProxy ? customApn.proxy : defaultApn.proxy;
this.port = useCustomProxyPort ? customApn.port : defaultApn.port;
this.username = useCustomUsername ? customApn.username : defaultApn.username;
this.password = useCustomPassword ? customApn.password : defaultApn.password;
}
public boolean hasProxy() {
return !TextUtils.isEmpty(proxy);
}
public String getMmsc() {
return mmsc;
}
public String getProxy() {
return hasProxy() ? proxy : null;
}
public int getPort() {
return TextUtils.isEmpty(port) ? 80 : Integer.parseInt(port);
}
public boolean hasAuthentication() {
return !TextUtils.isEmpty(username);
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
@Override
public @NonNull String toString() {
return Log.tag(Apn.class) +
"{ mmsc: \"" + mmsc + "\"" +
", proxy: " + (proxy == null ? "none" : '"' + proxy + '"') +
", port: " + (port == null ? "(none)" : port) +
", user: " + (username == null ? "none" : '"' + username + '"') +
", pass: " + (password == null ? "none" : '"' + password + '"') + " }";
}
}
}

View File

@@ -1,87 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import java.util.concurrent.TimeoutException;
public abstract class LollipopMmsConnection extends BroadcastReceiver {
private static final String TAG = Log.tag(LollipopMmsConnection.class);
private final Context context;
private final String action;
private boolean resultAvailable;
public abstract void onResult(Context context, Intent intent);
protected LollipopMmsConnection(Context context, String action) {
super();
this.context = context;
this.action = action;
}
@Override
public synchronized void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive()");
if (!action.equals(intent.getAction())) {
Log.w(TAG, "received broadcast with unexpected action " + intent.getAction());
return;
}
onResult(context, intent);
resultAvailable = true;
notifyAll();
}
protected void beginTransaction() {
getContext().getApplicationContext().registerReceiver(this, new IntentFilter(action));
}
protected void endTransaction() {
getContext().getApplicationContext().unregisterReceiver(this);
resultAvailable = false;
}
protected void waitForResult() throws TimeoutException {
long timeoutExpiration = System.currentTimeMillis() + 60000;
while (!resultAvailable) {
Util.wait(this, Math.max(1, timeoutExpiration - System.currentTimeMillis()));
if (System.currentTimeMillis() >= timeoutExpiration) {
throw new TimeoutException("timeout when waiting for MMS");
}
}
}
protected PendingIntent getPendingIntent() {
return PendingIntent.getBroadcast(getContext(), 1, new Intent(action), PendingIntentFlags.oneShot());
}
protected Context getContext() {
return context;
}
}

View File

@@ -33,10 +33,6 @@ public abstract class MediaConstraints {
return new PushMediaConstraints(sentMediaQuality);
}
public static MediaConstraints getMmsMediaConstraints(int subscriptionId) {
return new MmsMediaConstraints(subscriptionId);
}
public abstract int getImageMaxWidth(Context context);
public abstract int getImageMaxHeight(Context context);
public abstract int getImageMaxSize(Context context);

View File

@@ -1,36 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
public class MediaNotFoundException extends Exception {
public MediaNotFoundException() {
}
public MediaNotFoundException(String detailMessage) {
super(detailMessage);
}
public MediaNotFoundException(Throwable throwable) {
super(throwable);
}
public MediaNotFoundException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}

View File

@@ -1,40 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
public class MediaTooLargeException extends Exception {
public MediaTooLargeException() {
// TODO Auto-generated constructor stub
}
public MediaTooLargeException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public MediaTooLargeException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public MediaTooLargeException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

@@ -1,53 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.mms.service_alt.MmsConfig;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
final class MmsConfigManager {
private static final Map<Integer, MmsConfig> mmsConfigMap = new HashMap<>();
@WorkerThread
synchronized static @NonNull MmsConfig getMmsConfig(Context context, int subscriptionId) {
MmsConfig mmsConfig = mmsConfigMap.get(subscriptionId);
if (mmsConfig != null) {
return mmsConfig;
}
MmsConfig loadedConfig = loadMmsConfig(context, subscriptionId);
mmsConfigMap.put(subscriptionId, loadedConfig);
return loadedConfig;
}
private static @NonNull MmsConfig loadMmsConfig(Context context, int subscriptionId) {
Optional<SubscriptionInfoCompat> subscriptionInfo = new SubscriptionManagerCompat(context).getActiveSubscriptionInfo(subscriptionId);
if (subscriptionInfo.isPresent()) {
SubscriptionInfoCompat subscriptionInfoCompat = subscriptionInfo.get();
Configuration configuration = context.getResources().getConfiguration();
configuration.mcc = subscriptionInfoCompat.getMcc();
configuration.mnc = subscriptionInfoCompat.getMnc();
Context subContext = context.createConfigurationContext(configuration);
return new MmsConfig(subContext, subscriptionId);
}
return new MmsConfig(context, subscriptionId);
}
}

View File

@@ -1,79 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import com.android.mms.service_alt.MmsConfig;
final class MmsMediaConstraints extends MediaConstraints {
private final int subscriptionId;
private static final int MIN_IMAGE_DIMEN = 1024;
MmsMediaConstraints(int subscriptionId) {
this.subscriptionId = subscriptionId;
}
@Override
public int getImageMaxWidth(Context context) {
return Math.max(MIN_IMAGE_DIMEN, getOverriddenMmsConfig(context).getMaxImageWidth());
}
@Override
public int getImageMaxHeight(Context context) {
return Math.max(MIN_IMAGE_DIMEN, getOverriddenMmsConfig(context).getMaxImageHeight());
}
@Override
public int[] getImageDimensionTargets(Context context) {
int[] targets = new int[4];
targets[0] = getImageMaxHeight(context);
for (int i = 1; i < targets.length; i++) {
targets[i] = targets[i - 1] / 2;
}
return targets;
}
@Override
public int getImageMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getGifMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getVideoMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getUncompressedVideoMaxSize(Context context) {
return Math.max(getVideoMaxSize(context), 15 * 1024 * 1024);
}
@Override
public long getAudioMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getDocumentMaxSize(Context context) {
return getMaxMessageSize(context);
}
private int getMaxMessageSize(Context context) {
return getOverriddenMmsConfig(context).getMaxMessageSize();
}
private MmsConfig.Overridden getOverriddenMmsConfig(Context context) {
MmsConfig mmsConfig = MmsConfigManager.getMmsConfig(context, subscriptionId);
return new MmsConfig.Overridden(mmsConfig, null);
}
}

View File

@@ -1,165 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.PowerManager;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MmsRadio {
private static final String TAG = Log.tag(MmsRadio.class);
private static MmsRadio instance;
public static synchronized MmsRadio getInstance(Context context) {
if (instance == null)
instance = new MmsRadio(context.getApplicationContext());
return instance;
}
///
private static final String FEATURE_ENABLE_MMS = "enableMMS";
private static final int APN_ALREADY_ACTIVE = 0;
public static final int TYPE_MOBILE_MMS = 2;
private final Context context;
private ConnectivityManager connectivityManager;
private ConnectivityListener connectivityListener;
private PowerManager.WakeLock wakeLock;
private int connectedCounter = 0;
private MmsRadio(Context context) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
this.context = context;
this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
this.wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:mms");
this.wakeLock.setReferenceCounted(true);
}
public synchronized void disconnect() {
Log.i(TAG, "MMS Radio Disconnect Called...");
wakeLock.release();
connectedCounter--;
Log.i(TAG, "Reference count: " + connectedCounter);
if (connectedCounter == 0) {
Log.i(TAG, "Turning off MMS radio...");
try {
final Method stopUsingNetworkFeatureMethod = connectivityManager.getClass().getMethod("stopUsingNetworkFeature", Integer.TYPE, String.class);
stopUsingNetworkFeatureMethod.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
} catch (NoSuchMethodException nsme) {
Log.w(TAG, nsme);
} catch (IllegalAccessException iae) {
Log.w(TAG, iae);
} catch (InvocationTargetException ite) {
Log.w(TAG, ite);
}
if (connectivityListener != null) {
Log.i(TAG, "Unregistering receiver...");
context.unregisterReceiver(connectivityListener);
connectivityListener = null;
}
}
}
public synchronized void connect() throws MmsRadioException {
int status;
try {
final Method startUsingNetworkFeatureMethod = connectivityManager.getClass().getMethod("startUsingNetworkFeature", Integer.TYPE, String.class);
status = (int)startUsingNetworkFeatureMethod.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
} catch (NoSuchMethodException nsme) {
throw new MmsRadioException(nsme);
} catch (IllegalAccessException iae) {
throw new MmsRadioException(iae);
} catch (InvocationTargetException ite) {
throw new MmsRadioException(ite);
}
Log.i(TAG, "startUsingNetworkFeature status: " + status);
if (status == APN_ALREADY_ACTIVE) {
wakeLock.acquire();
connectedCounter++;
return;
} else {
wakeLock.acquire();
connectedCounter++;
if (connectivityListener == null) {
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
connectivityListener = new ConnectivityListener();
context.registerReceiver(connectivityListener, filter);
}
Util.wait(this, 30000);
if (!isConnected()) {
Log.w(TAG, "Got back from connectivity wait, and not connected...");
disconnect();
throw new MmsRadioException("Unable to successfully enable MMS radio.");
}
}
}
private boolean isConnected() {
NetworkInfo info = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
Log.i(TAG, "Connected: " + info);
if ((info == null) || (info.getType() != TYPE_MOBILE_MMS) || !info.isConnected())
return false;
return true;
}
private boolean isConnectivityPossible() {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
return networkInfo != null && networkInfo.isAvailable();
}
private boolean isConnectivityFailure() {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
return networkInfo == null || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED;
}
private synchronized void issueConnectivityChange() {
if (isConnected()) {
Log.i(TAG, "Notifying connected...");
notifyAll();
return;
}
if (!isConnected() && (isConnectivityFailure() || !isConnectivityPossible())) {
Log.i(TAG, "Notifying not connected...");
notifyAll();
return;
}
}
private class ConnectivityListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Got connectivity change...");
issueConnectivityChange();
}
}
}

View File

@@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.mms;
public class MmsRadioException extends Throwable {
public MmsRadioException(String s) {
super(s);
}
public MmsRadioException(Exception e) {
super(e);
}
}

View File

@@ -1,20 +0,0 @@
package org.thoughtcrime.securesms.mms;
public class MmsSendResult {
private final byte[] messageId;
private final int responseStatus;
public MmsSendResult(byte[] messageId, int responseStatus) {
this.messageId = messageId;
this.responseStatus = responseStatus;
}
public int getResponseStatus() {
return responseStatus;
}
public byte[] getMessageId() {
return messageId;
}
}

View File

@@ -1,163 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.SendConf;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPostHC4;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntityHC4;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.IOException;
@SuppressWarnings("deprecation")
public class OutgoingLegacyMmsConnection extends LegacyMmsConnection implements OutgoingMmsConnection {
private final static String TAG = Log.tag(OutgoingLegacyMmsConnection.class);
public OutgoingLegacyMmsConnection(Context context) throws ApnUnavailableException {
super(context);
}
private HttpUriRequest constructRequest(byte[] pduBytes, boolean useProxy)
throws IOException
{
try {
HttpPostHC4 request = new HttpPostHC4(apn.getMmsc());
for (Header header : getBaseHeaders()) {
request.addHeader(header);
}
request.setEntity(new ByteArrayEntityHC4(pduBytes));
if (useProxy) {
HttpHost proxy = new HttpHost(apn.getProxy(), apn.getPort());
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
}
return request;
} catch (IllegalArgumentException iae) {
throw new IOException(iae);
}
}
public void sendNotificationReceived(byte[] pduBytes, boolean usingMmsRadio, boolean useProxyIfAvailable)
throws IOException
{
sendBytes(pduBytes, usingMmsRadio, useProxyIfAvailable);
}
@Override
public @Nullable SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException {
try {
MmsRadio radio = MmsRadio.getInstance(context);
if (isDirectConnect()) {
Log.i(TAG, "Sending MMS directly without radio change...");
try {
return send(pduBytes, false, false);
} catch (IOException e) {
Log.w(TAG, e);
}
}
Log.i(TAG, "Sending MMS with radio change and proxy...");
radio.connect();
try {
try {
return send(pduBytes, true, true);
} catch (IOException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Sending MMS with radio change and without proxy...");
try {
return send(pduBytes, true, false);
} catch (IOException ioe) {
Log.w(TAG, ioe);
throw new UndeliverableMessageException(ioe);
}
} finally {
radio.disconnect();
}
} catch (MmsRadioException e) {
Log.w(TAG, e);
throw new UndeliverableMessageException(e);
}
}
private SendConf send(byte[] pduBytes, boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
byte[] response = sendBytes(pduBytes, useMmsRadio, useProxyIfAvailable);
return (SendConf) new PduParser(response).parse();
}
private byte[] sendBytes(byte[] pduBytes, boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
final boolean useProxy = useProxyIfAvailable && apn.hasProxy();
final String targetHost = useProxy
? apn.getProxy()
: Uri.parse(apn.getMmsc()).getHost();
Log.i(TAG, "Sending MMS of length: " + pduBytes.length
+ (useMmsRadio ? ", using mms radio" : "")
+ (useProxy ? ", using proxy" : ""));
try {
if (checkRouteToHost(context, targetHost, useMmsRadio)) {
Log.i(TAG, "got successful route to host " + targetHost);
byte[] response = execute(constructRequest(pduBytes, useProxy));
if (response != null) return response;
}
} catch (IOException ioe) {
Log.w(TAG, ioe);
}
throw new IOException("Connection manager could not obtain route to host.");
}
public static boolean isConnectionPossible(Context context) {
try {
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS);
if (networkInfo == null) {
Log.w(TAG, "MMS network info was null, unsupported by this device");
return false;
}
getApn(context);
return true;
} catch (ApnUnavailableException e) {
Log.w(TAG, e);
return false;
}
}
}

View File

@@ -1,113 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.mms;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.telephony.SmsManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.mms.service_alt.MmsConfig;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.SendConf;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.providers.MmsBodyProvider;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class OutgoingLollipopMmsConnection extends LollipopMmsConnection implements OutgoingMmsConnection {
private static final String TAG = Log.tag(OutgoingLollipopMmsConnection.class);
private static final String ACTION = OutgoingLollipopMmsConnection.class.getCanonicalName() + "MMS_SENT_ACTION";
private byte[] response;
public OutgoingLollipopMmsConnection(Context context) {
super(context, ACTION);
}
@TargetApi(22)
@Override
public synchronized void onResult(Context context, Intent intent) {
if (VERSION.SDK_INT >= 22) {
Log.i(TAG, "HTTP status: " + intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, -1));
}
response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
}
@Override
public @Nullable synchronized SendConf send(@NonNull byte[] pduBytes, int subscriptionId)
throws UndeliverableMessageException
{
beginTransaction();
try {
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
StreamUtil.copy(new ByteArrayInputStream(pduBytes), pointer.getOutputStream());
SmsManager smsManager;
if (VERSION.SDK_INT >= 22 && subscriptionId != -1) {
smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
smsManager = SmsManager.getDefault();
}
Bundle configOverrides = new Bundle();
configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, true);
MmsConfig mmsConfig = MmsConfigManager.getMmsConfig(getContext(), subscriptionId);
if (mmsConfig != null) {
MmsConfig.Overridden overridden = new MmsConfig.Overridden(mmsConfig, new Bundle());
configOverrides.putString(SmsManager.MMS_CONFIG_HTTP_PARAMS, overridden.getHttpParams());
configOverrides.putInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE, overridden.getMaxMessageSize());
}
smsManager.sendMultimediaMessage(getContext(),
pointer.getUri(),
null,
configOverrides,
getPendingIntent());
waitForResult();
Log.i(TAG, "MMS broadcast received and processed.");
pointer.close();
if (response == null) {
throw new UndeliverableMessageException("Null response.");
}
return (SendConf) new PduParser(response).parse();
} catch (IOException | TimeoutException e) {
throw new UndeliverableMessageException(e);
} finally {
endTransaction();
}
}
}

View File

@@ -1,14 +0,0 @@
package org.thoughtcrime.securesms.mms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.SendConf;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
public interface OutgoingMmsConnection {
@Nullable
SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException;
}

View File

@@ -1,98 +0,0 @@
package org.thoughtcrime.securesms.mms;
import com.google.android.mms.ContentType;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.PduBody;
import com.google.android.mms.pdu_alt.PduPart;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
public class PartParser {
private static final String TAG = Log.tag(PartParser.class);
private static final List<String> DOCUMENT_TYPES = Arrays.asList("text/vcard", "text/x-vcard");
public static String getMessageText(PduBody body) {
String bodyText = null;
for (int i=0;i<body.getPartsNum();i++) {
if (isText(body.getPart(i)) && !isDocument(body.getPart(i))) {
String partText;
try {
String characterSet = CharacterSets.getMimeName(body.getPart(i).getCharset());
if (characterSet.equals(CharacterSets.MIMENAME_ANY_CHARSET))
characterSet = CharacterSets.MIMENAME_UTF_8;
if (body.getPart(i).getData() != null) {
partText = new String(body.getPart(i).getData(), characterSet);
} else {
partText = "";
}
} catch (UnsupportedEncodingException e) {
Log.w(TAG, e);
partText = "Unsupported Encoding!";
}
bodyText = (bodyText == null) ? partText : bodyText + " " + partText;
}
}
return bodyText;
}
public static PduBody getSupportedMediaParts(PduBody body) {
PduBody stripped = new PduBody();
for (int i=0;i<body.getPartsNum();i++) {
if (isDisplayableMedia(body.getPart(i)) || isDocument(body.getPart(i))) {
stripped.addPart(body.getPart(i));
}
}
return stripped;
}
public static int getSupportedMediaPartCount(PduBody body) {
int partCount = 0;
for (int i=0;i<body.getPartsNum();i++) {
if (isDisplayableMedia(body.getPart(i)) || isDocument(body.getPart(i))) {
partCount++;
}
}
return partCount;
}
public static boolean isImage(PduPart part) {
return ContentType.isImageType(Util.toIsoString(part.getContentType()));
}
public static boolean isAudio(PduPart part) {
return ContentType.isAudioType(Util.toIsoString(part.getContentType()));
}
public static boolean isVideo(PduPart part) {
return ContentType.isVideoType(Util.toIsoString(part.getContentType()));
}
public static boolean isText(PduPart part) {
return ContentType.isTextType(Util.toIsoString(part.getContentType()));
}
public static boolean isDisplayableMedia(PduPart part) {
return isImage(part) || isAudio(part) || isVideo(part);
}
public static boolean isDocument(PduPart part) {
return DOCUMENT_TYPES.contains(Util.toIsoString(part.getContentType()).toLowerCase());
}
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.preferences;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class MmsPreferencesActivity extends PassphraseRequiredActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle icicle, boolean ready) {
assert getSupportActionBar() != null;
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Fragment fragment = new MmsPreferencesFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(android.R.id.content, fragment);
fragmentTransaction.commit();
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return false;
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
}

View File

@@ -1,95 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.preferences;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
import org.thoughtcrime.securesms.database.ApnDatabase;
import org.thoughtcrime.securesms.mms.LegacyMmsConnection;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.IOException;
public class MmsPreferencesFragment extends CorrectedPreferenceFragment {
private static final String TAG = Log.tag(MmsPreferencesFragment.class);
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_manual_mms);
}
@Override
public void onResume() {
super.onResume();
new LoadApnDefaultsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private class LoadApnDefaultsTask extends AsyncTask<Void, Void, LegacyMmsConnection.Apn> {
@Override
protected LegacyMmsConnection.Apn doInBackground(Void... params) {
try {
Context context = getActivity();
if (context != null) {
return ApnDatabase.getInstance(context)
.getDefaultApnParameters(TelephonyUtil.getMccMnc(context),
TelephonyUtil.getApn(context));
}
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
@Override
protected void onPostExecute(LegacyMmsConnection.Apn apnDefaults) {
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_HOST_PREF))
.setValidator(new CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.UriValidator())
.setDefaultValue(apnDefaults.getMmsc());
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_PROXY_HOST_PREF))
.setValidator(new CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.HostnameValidator())
.setDefaultValue(apnDefaults.getProxy());
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_PROXY_PORT_PREF))
.setValidator(new CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.PortValidator())
.setDefaultValue(apnDefaults.getPort());
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_USERNAME_PREF))
.setDefaultValue(apnDefaults.getPort());
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_PASSWORD_PREF))
.setDefaultValue(apnDefaults.getPassword());
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMS_USER_AGENT))
.setDefaultValue(LegacyMmsConnection.USER_AGENT);
}
}
}

View File

@@ -1,144 +0,0 @@
/**
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.providers;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
public final class MmsBodyProvider extends BaseContentProvider {
private static final String TAG = Log.tag(MmsBodyProvider.class);
private static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".mms";
private static final String CONTENT_URI_STRING = "content://" + CONTENT_AUTHORITY + "/mms";
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
private static final int SINGLE_ROW = 1;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(CONTENT_AUTHORITY, "mms/#", SINGLE_ROW);
}
@Override
public boolean onCreate() {
return true;
}
private File getFile(Uri uri) {
long id = Long.parseLong(uri.getPathSegments().get(1));
return new File(getContext().getCacheDir(), id + ".mmsbody");
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
Log.i(TAG, "openFile(" + uri + ", " + mode + ")");
switch (uriMatcher.match(uri)) {
case SINGLE_ROW:
Log.i(TAG, "Fetching message body for a single row...");
File tmpFile = getFile(uri);
final int fileMode;
switch (mode) {
case "w": fileMode = ParcelFileDescriptor.MODE_TRUNCATE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_WRITE_ONLY; break;
case "r": fileMode = ParcelFileDescriptor.MODE_READ_ONLY; break;
default: throw new IllegalArgumentException("requested file mode unsupported");
}
Log.i(TAG, "returning file " + tmpFile.getAbsolutePath());
return ParcelFileDescriptor.open(tmpFile, fileMode);
}
throw new FileNotFoundException("Request for bad message.");
}
@Override
public int delete(@NonNull Uri uri, String arg1, String[] arg2) {
switch (uriMatcher.match(uri)) {
case SINGLE_ROW:
return getFile(uri).delete() ? 1 : 0;
}
return 0;
}
@Override
public String getType(@NonNull Uri arg0) {
return null;
}
@Override
public Uri insert(@NonNull Uri arg0, ContentValues arg1) {
return null;
}
@Override
public Cursor query(@NonNull Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
return null;
}
@Override
public int update(@NonNull Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
public static Pointer makeTemporaryPointer(Context context) {
return new Pointer(context, ContentUris.withAppendedId(MmsBodyProvider.CONTENT_URI, System.currentTimeMillis()));
}
public static class Pointer {
private final Context context;
private final Uri uri;
public Pointer(Context context, Uri uri) {
this.context = context;
this.uri = uri;
}
public Uri getUri() {
return uri;
}
public OutputStream getOutputStream() throws FileNotFoundException {
return context.getContentResolver().openOutputStream(uri, "w");
}
public InputStream getInputStream() throws FileNotFoundException {
return context.getContentResolver().openInputStream(uri);
}
public void close() {
context.getContentResolver().delete(uri, null, null);
}
}
}

View File

@@ -240,7 +240,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
unblockButton.setVisibility(View.GONE);
}
boolean isAudioAvailable = (recipient.isRegistered() || SignalStore.misc().getSmsExportPhase().allowSmsFeatures()) &&
boolean isAudioAvailable = recipient.isRegistered() &&
!recipient.isGroup() &&
!recipient.isBlocked() &&
!recipient.isSelf() &&

View File

@@ -83,7 +83,6 @@ public final class GroupLinkBottomSheetDialogFragment extends BottomSheetDialogF
MultiselectForwardFragment.showBottomSheet(
getParentFragmentManager(),
new MultiselectForwardFragmentArgs(
true,
Collections.singletonList(new MultiShareArgs.Builder()
.withDraftText(groupLink.getUrl())
.build()),

View File

@@ -368,7 +368,7 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
}
public void showErrorDialog(Context context, String msg, DialogInterface.OnClickListener positiveButtonListener) {
new MaterialAlertDialogBuilder(context).setMessage(msg).setPositiveButton(R.string.ok, positiveButtonListener).show();
new MaterialAlertDialogBuilder(context).setMessage(msg).setPositiveButton(android.R.string.ok, positiveButtonListener).show();
}
@Override

View File

@@ -1,59 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.app.IntentService;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.widget.Toast;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.OutgoingMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Rfc5724Uri;
import java.net.URISyntaxException;
import java.net.URLDecoder;
public class QuickResponseService extends IntentService {
private static final String TAG = Log.tag(QuickResponseService.class);
public QuickResponseService() {
super("QuickResponseService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(intent.getAction())) {
Log.w(TAG, "Received unknown intent: " + intent.getAction());
return;
}
if (KeyCachingService.isLocked(this)) {
Log.w(TAG, "Got quick response request when locked...");
Toast.makeText(this, R.string.QuickResponseService_quick_response_unavailable_when_Signal_is_locked, Toast.LENGTH_LONG).show();
return;
}
try {
Rfc5724Uri uri = new Rfc5724Uri(intent.getDataString());
String content = intent.getStringExtra(Intent.EXTRA_TEXT);
String number = uri.getPath();
if (number.contains("%")){
number = URLDecoder.decode(number);
}
Recipient recipient = Recipient.external(this, number);
if (!TextUtils.isEmpty(content)) {
MessageSender.send(this, OutgoingMessage.sms(recipient, content), -1, MessageSender.SendType.SIGNAL, null, null);
}
} catch (URISyntaxException e) {
Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show();
Log.w(TAG, e);
}
}
}

View File

@@ -1,69 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.SmsSentJob;
public class SmsDeliveryListener extends BroadcastReceiver {
private static final String TAG = Log.tag(SmsDeliveryListener.class);
public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION";
public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
@Override
public void onReceive(Context context, Intent intent) {
JobManager jobManager = ApplicationDependencies.getJobManager();
long messageId = intent.getLongExtra("message_id", -1);
int runAttempt = intent.getIntExtra("run_attempt", 0);
boolean isMultipart = intent.getBooleanExtra("is_multipart", true);
switch (intent.getAction()) {
case SENT_SMS_ACTION:
int result = getResultCode();
jobManager.add(new SmsSentJob(messageId, isMultipart, SENT_SMS_ACTION, result, runAttempt));
break;
case DELIVERED_SMS_ACTION:
byte[] pdu = intent.getByteArrayExtra("pdu");
if (pdu == null) {
Log.w(TAG, "No PDU in delivery receipt!");
break;
}
SmsMessage message = SmsMessage.createFromPdu(pdu);
if (message == null) {
Log.w(TAG, "Delivery receipt failed to parse!");
break;
}
int status = message.getStatus();
Log.i(TAG, "Original status: " + status);
// Note: https://developer.android.com/reference/android/telephony/SmsMessage.html#getStatus()
// " CDMA: For not interfering with status codes from GSM, the value is shifted to the bits 31-16"
// Note: https://stackoverflow.com/a/33240109
if ("3gpp2".equals(intent.getStringExtra("format"))) {
Log.w(TAG, "Correcting for CDMA delivery receipt...");
if (status >> 24 <= 0) status = MessageTable.Status.STATUS_COMPLETE;
else if (status >> 24 == 2) status = MessageTable.Status.STATUS_PENDING;
else if (status >> 24 == 3) status = MessageTable.Status.STATUS_FAILED;
}
jobManager.add(new SmsSentJob(messageId, isMultipart, DELIVERED_SMS_ACTION, status, runAttempt));
break;
default:
Log.w(TAG, "Unknown action: " + intent.getAction());
}
}
}

View File

@@ -129,7 +129,6 @@ public final class ShakeToReport implements ShakeDetector.Listener {
MultiselectForwardFragment.showFullScreen(
activity.getSupportFragmentManager(),
new MultiselectForwardFragmentArgs(
true,
Collections.singletonList(new MultiShareArgs.Builder()
.withDraftText(url)
.build()),

View File

@@ -85,7 +85,6 @@ public final class MultiShareSender {
public static MultiShareSendResultCollection sendSync(@NonNull MultiShareArgs multiShareArgs) {
List<MultiShareSendResult> results = new ArrayList<>(multiShareArgs.getContactSearchKeys().size());
Context context = ApplicationDependencies.getApplication();
boolean isMmsEnabled = Util.isMmsCapable(context);
String message = multiShareArgs.getDraftText();
SlideDeck slideDeck;
List<OutgoingMessage> storiesBatch = new LinkedList<>();
@@ -111,8 +110,7 @@ public final class MultiShareSender {
MessageSendType sendType = MessageSendType.SignalMessageSendType.INSTANCE;
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
List<Contact> contacts = multiShareArgs.getSharedContacts();
boolean needsSplit = !sendType.usesSmsTransport() &&
message != null &&
boolean needsSplit = message != null &&
message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
boolean hasMmsMedia = !multiShareArgs.getMedia().isEmpty() ||
(multiShareArgs.getDataUri() != null && multiShareArgs.getDataUri() != Uri.EMPTY) ||
@@ -128,9 +126,9 @@ public final class MultiShareSender {
MultiShareTimestampProvider sentTimestamp = recipient.isDistributionList() ? distributionListSentTimestamps : MultiShareTimestampProvider.create();
boolean canSendAsTextStory = recipientSearchKey.isStory() && multiShareArgs.isValidForTextStoryGeneration();
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) {
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent())) {
results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.MMS_NOT_ENABLED));
} else if (hasMmsMedia && sendType.usesSmsTransport() || hasPushMedia && !sendType.usesSmsTransport() || canSendAsTextStory) {
} else if (hasPushMedia || canSendAsTextStory) {
sendMediaMessageOrCollectStoryToBatch(context,
multiShareArgs,
recipient,

View File

@@ -6,13 +6,9 @@ import java.lang.UnsupportedOperationException
sealed class ResolvedShareData {
abstract val isMmsOrSmsSupported: Boolean
abstract fun toMultiShareArgs(): MultiShareArgs
data class Primitive(val text: CharSequence) : ResolvedShareData() {
override val isMmsOrSmsSupported: Boolean = true
override fun toMultiShareArgs(): MultiShareArgs {
return MultiShareArgs.Builder(setOf()).withDraftText(text.toString()).build()
}
@@ -21,8 +17,7 @@ sealed class ResolvedShareData {
data class ExternalUri(
val uri: Uri,
val mimeType: String,
val text: CharSequence?,
override val isMmsOrSmsSupported: Boolean
val text: CharSequence?
) : ResolvedShareData() {
override fun toMultiShareArgs(): MultiShareArgs {
return MultiShareArgs.Builder(setOf()).withDataUri(uri).withDataType(mimeType).withDraftText(text?.toString()).build()
@@ -30,8 +25,7 @@ sealed class ResolvedShareData {
}
data class Media(
val media: List<org.thoughtcrime.securesms.mediasend.Media>,
override val isMmsOrSmsSupported: Boolean
val media: List<org.thoughtcrime.securesms.mediasend.Media>
) : ResolvedShareData() {
override fun toMultiShareArgs(): MultiShareArgs {
return MultiShareArgs.Builder(setOf()).withMedia(media).build()
@@ -39,7 +33,6 @@ sealed class ResolvedShareData {
}
object Failure : ResolvedShareData() {
override val isMmsOrSmsSupported: Boolean get() = throw UnsupportedOperationException()
override fun toMultiShareArgs(): MultiShareArgs = throw UnsupportedOperationException()
}
}

View File

@@ -232,7 +232,6 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C
R.id.fragment_container,
MultiselectForwardFragment.create(
MultiselectForwardFragmentArgs(
canSendToNonPush = resolvedShareData.isMmsOrSmsSupported,
multiShareArgs = listOf(resolvedShareData.toMultiShareArgs()),
title = getTitleFromExtras(),
forceDisableAddMessage = true,

View File

@@ -1,28 +1,19 @@
package org.thoughtcrime.securesms.sharing.v2
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.OpenableColumns
import androidx.annotation.NonNull
import androidx.annotation.WorkerThread
import androidx.core.content.ContextCompat
import androidx.core.util.toKotlinPair
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.UriUtil
import org.thoughtcrime.securesms.util.Util
import java.io.IOException
import java.io.InputStream
import java.util.Optional
@@ -73,8 +64,7 @@ class ShareRepository(context: Context) {
return ResolvedShareData.ExternalUri(
uri = blobUri,
mimeType = mimeType,
text = multiShareExternal.text,
isMmsOrSmsSupported = isMmsSupported(appContext, asUriAttachment(blobUri, mimeType, size))
text = multiShareExternal.text
)
}
@@ -131,9 +121,7 @@ class ShareRepository(context: Context) {
}.filterNotNull()
return if (media.isNotEmpty()) {
val isMmsSupported = media.all { isMmsSupported(appContext, asUriAttachment(it.uri, it.mimeType, it.size)) }
ResolvedShareData.Media(media, isMmsSupported)
ResolvedShareData.Media(media)
} else {
ResolvedShareData.Failure
}
@@ -179,21 +167,5 @@ class ShareRepository(context: Context) {
}
return null
}
private fun asUriAttachment(uri: Uri, mimeType: String, size: Long): UriAttachment {
return UriAttachment(uri, mimeType, -1, size, null, false, false, false, false, null, null, null, null, null)
}
private fun isMmsSupported(context: Context, attachment: Attachment): Boolean {
val canReadPhoneState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
if (!Util.isDefaultSmsProvider(context) || !canReadPhoneState || !Util.isMmsCapable(context) || !SignalStore.misc().smsExportPhase.allowSmsFeatures()) {
return false
}
val sendType: MessageSendType = MessageSendType.getFirstForTransport(MessageSendType.TransportType.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1)
return mmsConstraints.isSatisfied(context, attachment) || mmsConstraints.canResize(attachment)
}
}
}

View File

@@ -52,14 +52,12 @@ import org.thoughtcrime.securesms.jobs.AttachmentCompressionJob;
import org.thoughtcrime.securesms.jobs.AttachmentCopyJob;
import org.thoughtcrime.securesms.jobs.AttachmentMarkUploadedJob;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.ProfileKeySendJob;
import org.thoughtcrime.securesms.jobs.PushDistributionListSendJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.IndividualSendJob;
import org.thoughtcrime.securesms.jobs.ReactionSendJob;
import org.thoughtcrime.securesms.jobs.RemoteDeleteSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediasend.Media;
@@ -536,10 +534,8 @@ public class MessageSender {
sendDistributionList(context, recipient, messageId, Collections.emptySet(), uploadJobIds);
} else if (sendType == SendType.SIGNAL && isPushMediaSend(context, recipient)) {
sendMediaPush(context, recipient, messageId, uploadJobIds);
} else if (sendType == SendType.MMS) {
sendMms(context, messageId);
} else {
sendSms(recipient, messageId);
Log.w(TAG, "Unknown send type!");
}
}
@@ -576,16 +572,6 @@ public class MessageSender {
}
}
private static void sendMms(Context context, long messageId) {
JobManager jobManager = ApplicationDependencies.getJobManager();
MmsSendJob.enqueue(context, jobManager, messageId);
}
private static void sendSms(Recipient recipient, long messageId) {
JobManager jobManager = ApplicationDependencies.getJobManager();
jobManager.add(new SmsSendJob(messageId, recipient));
}
private static boolean isPushMediaSend(Context context, Recipient recipient) {
if (!SignalStore.account().isRegistered()) {
return false;

View File

@@ -100,7 +100,6 @@ public final class StickerManagementActivity extends PassphraseRequiredActivity
MultiselectForwardFragment.showBottomSheet(
getSupportFragmentManager(),
new MultiselectForwardFragmentArgs(
true,
Collections.singletonList(new MultiShareArgs.Builder()
.withDraftText(StickerUrl.createShareLink(packId, packKey))
.build()),

View File

@@ -242,7 +242,6 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity
MultiselectForwardFragment.showBottomSheet(
getSupportFragmentManager(),
new MultiselectForwardFragmentArgs(
true,
Collections.singletonList(new MultiShareArgs.Builder()
.withDraftText(StickerUrl.createShareLink(packId, packKey))
.build()),

View File

@@ -153,7 +153,7 @@ public final class StorageSyncHelper {
.setPinnedConversations(StorageSyncModels.localToRemotePinnedConversations(pinned))
.setPreferContactAvatars(SignalStore.settings().isPreferSystemContactPhotos())
.setPayments(SignalStore.paymentsValues().mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.paymentsValues().getPaymentsEntropy()).map(Entropy::getBytes).orElse(null))
.setPrimarySendsSms(Util.isDefaultSmsProvider(context))
.setPrimarySendsSms(false)
.setUniversalExpireTimer(SignalStore.settings().getUniversalExpireTimer())
.setDefaultReactions(SignalStore.emojiValues().getReactions())
.setSubscriber(StorageSyncModels.localToRemoteSubscriber(SignalStore.donationsValues().getSubscriber()))

View File

@@ -1,87 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.util;
import android.os.Parcel;
import android.telephony.SmsMessage;
import org.signal.core.util.logging.Log;
public class SmsCharacterCalculator extends CharacterCalculator {
private static final String TAG = Log.tag(SmsCharacterCalculator.class);
@Override
public CharacterState calculateCharacters(String messageBody) {
int[] length;
int messagesSpent;
int charactersSpent;
int charactersRemaining;
try {
length = SmsMessage.calculateLength(messageBody, false);
messagesSpent = length[0];
charactersSpent = length[1];
charactersRemaining = length[2];
} catch (NullPointerException e) {
Log.w(TAG, e);
messagesSpent = 1;
charactersSpent = messageBody.length();
charactersRemaining = 1000;
} catch (RuntimeException e) {
if (e instanceof SecurityException || e.getCause() instanceof SecurityException) {
Log.e(TAG, "Security Exception", e);
messagesSpent = 1;
charactersSpent = messageBody.length();
charactersRemaining = 1000;
} else {
throw e;
}
}
int maxMessageSize;
if (messagesSpent > 0) {
maxMessageSize = (charactersSpent + charactersRemaining) / messagesSpent;
} else {
maxMessageSize = (charactersSpent + charactersRemaining);
}
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<SmsCharacterCalculator> CREATOR = new Creator<SmsCharacterCalculator>() {
@Override
public SmsCharacterCalculator createFromParcel(Parcel in) {
return new SmsCharacterCalculator();
}
@Override
public SmsCharacterCalculator[] newArray(int size) {
return new SmsCharacterCalculator[size];
}
};
}

View File

@@ -1,30 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.provider.Telephony;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
public final class SmsUtil {
private SmsUtil() {
}
/**
* Must be used with {@code startActivityForResult}
*/
public static @NonNull Intent getSmsRoleIntent(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 29) {
RoleManager roleManager = ContextCompat.getSystemService(context, RoleManager.class);
return roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS);
} else {
Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.getPackageName());
return intent;
}
}
}

View File

@@ -24,9 +24,6 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.Telephony;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.SpannableString;
@@ -38,8 +35,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import com.annimon.stream.Stream;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
@@ -50,11 +45,10 @@ import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
@@ -143,10 +137,6 @@ public class Util {
return out.toString();
}
public static boolean isEmpty(EncodedStringValue[] value) {
return value == null || value.length == 0;
}
public static boolean isEmpty(ComposeText value) {
return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed());
}
@@ -198,27 +188,15 @@ public class Util {
}
public static @NonNull String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
throw new AssertionError("ISO_8859_1 must be supported!");
}
return new String(bytes, StandardCharsets.ISO_8859_1);
}
public static byte[] toIsoBytes(String isoString) {
try {
return isoString.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
throw new AssertionError("ISO_8859_1 must be supported!");
}
return isoString.getBytes(StandardCharsets.ISO_8859_1);
}
public static byte[] toUtf8Bytes(String utf8String) {
try {
return utf8String.getBytes(CharacterSets.MIMENAME_UTF_8);
} catch (UnsupportedEncodingException e) {
throw new AssertionError("UTF_8 must be supported!");
}
return utf8String.getBytes(StandardCharsets.UTF_8);
}
public static void wait(Object lock, long timeout) {
@@ -231,7 +209,6 @@ public class Util {
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PHONE_STATE,
android.Manifest.permission.READ_SMS,
android.Manifest.permission.READ_PHONE_NUMBERS
})
@SuppressLint("MissingPermission")
@@ -328,11 +305,6 @@ public class Util {
return result;
}
@SuppressLint("NewApi")
public static boolean isDefaultSmsProvider(Context context){
return context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context));
}
/**
* The app version.
* <p>
@@ -394,10 +366,6 @@ public class Util {
}
}
public static boolean isMmsCapable(Context context) {
return (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) || OutgoingLegacyMmsConnection.isConnectionPossible(context);
}
public static <T> T getRandomElement(T[] elements) {
return elements[new SecureRandom().nextInt(elements.length)];
}