diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7088252e8d..51e7053193 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1313,6 +1313,7 @@
Connecting...
Permission required
Continue
+ Signal needs SMS permission in order to send an SMS, but it has been permanently denied. Please continue to app settings, select \"Permissions\" and enable \"SMS\".
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index afcc0d358a..259b10f653 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -112,7 +112,6 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
-import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
@@ -1663,48 +1662,49 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId, initiating);
}
- private ListenableFuture sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating)
- throws InvalidMessageException
- {
- final SettableFuture future = new SettableFuture<>();
- final Context context = getApplicationContext();
- OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(recipient,
- slideDeck,
- body,
- System.currentTimeMillis(),
- subscriptionId,
- expiresIn,
- distributionType);
+ private ListenableFuture sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating) {
+
+ OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType);
+
+ final SettableFuture future = new SettableFuture<>();
+ final Context context = getApplicationContext();
+
+ final OutgoingMediaMessage outgoingMessage;
if (isSecureText && !forceSms) {
- outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);
+ outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate);
+ } else {
+ outgoingMessage = outgoingMessageCandidate;
}
- attachmentManager.clear(glideRequests, false);
- composeText.setText("");
- final long id = fragment.stageOutgoingMessage(outgoingMessage);
+ Permissions.with(this)
+ .request(Manifest.permission.SEND_SMS)
+ .ifNecessary(isSecureText || forceSms)
+ .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms))
+ .onAllGranted(() -> {
+ attachmentManager.clear(glideRequests, false);
+ composeText.setText("");
+ final long id = fragment.stageOutgoingMessage(outgoingMessage);
- new AsyncTask() {
- @Override
- protected Long doInBackground(OutgoingMediaMessage... messages) {
- if (initiating) {
- DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
- }
+ new AsyncTask() {
+ @Override
+ protected Long doInBackground(Void... param) {
+ if (initiating) {
+ DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
+ }
- return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() {
- @Override
- public void onComplete() {
- fragment.releaseOutgoingMessage(id);
- }
- });
- }
+ return MessageSender.send(context, masterSecret, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
+ }
- @Override
- protected void onPostExecute(Long result) {
- sendComplete(result);
- future.set(null);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, outgoingMessage);
+ @Override
+ protected void onPostExecute(Long result) {
+ sendComplete(result);
+ future.set(null);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ })
+ .onAnyDenied(() -> future.set(null))
+ .execute();
return future;
}
@@ -1712,38 +1712,43 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void sendTextMessage(final boolean forceSms, final long expiresIn, final int subscriptionId, final boolean initiatingConversation)
throws InvalidMessageException
{
- final Context context = getApplicationContext();
+ final Context context = getApplicationContext();
+ final String messageBody = getMessage();
+
OutgoingTextMessage message;
if (isSecureText && !forceSms) {
- message = new OutgoingEncryptedMessage(recipient, getMessage(), expiresIn);
+ message = new OutgoingEncryptedMessage(recipient, messageBody, expiresIn);
} else {
- message = new OutgoingTextMessage(recipient, getMessage(), expiresIn, subscriptionId);
+ message = new OutgoingTextMessage(recipient, messageBody, expiresIn, subscriptionId);
}
- this.composeText.setText("");
- final long id = fragment.stageOutgoingMessage(message);
+ Permissions.with(this)
+ .request(Manifest.permission.SEND_SMS)
+ .ifNecessary(forceSms || !isSecureText)
+ .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms))
+ .onAllGranted(() -> {
+ this.composeText.setText("");
+ final long id = fragment.stageOutgoingMessage(message);
- new AsyncTask() {
- @Override
- protected Long doInBackground(OutgoingTextMessage... messages) {
- if (initiatingConversation) {
- DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
- }
+ new AsyncTask() {
+ @Override
+ protected Long doInBackground(OutgoingTextMessage... messages) {
+ if (initiatingConversation) {
+ DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
+ }
- return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() {
- @Override
- public void onComplete() {
- fragment.releaseOutgoingMessage(id);
- }
- });
- }
+ return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
+ }
- @Override
- protected void onPostExecute(Long result) {
- sendComplete(result);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
+ @Override
+ protected void onPostExecute(Long result) {
+ sendComplete(result);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
+
+ })
+ .execute();
}
private void updateToggleButtonState() {
@@ -1830,31 +1835,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
future.addListener(new ListenableFuture.Listener>() {
@Override
public void onSuccess(final @NonNull Pair result) {
- try {
- boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
- int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
- long expiresIn = recipient.getExpireMessages() * 1000;
- boolean initiating = threadId == -1;
- AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true);
- SlideDeck slideDeck = new SlideDeck();
- slideDeck.addSlide(audioSlide);
+ boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
+ int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
+ long expiresIn = recipient.getExpireMessages() * 1000;
+ boolean initiating = threadId == -1;
+ AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true);
+ SlideDeck slideDeck = new SlideDeck();
+ slideDeck.addSlide(audioSlide);
- sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId, initiating).addListener(new AssertedSuccessListener() {
- @Override
- public void onSuccess(Void nothing) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- });
- } catch (InvalidMessageException e) {
- Log.w(TAG, e);
- Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_error_sending_voice_message, Toast.LENGTH_LONG).show();
- }
+ sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId, initiating).addListener(new AssertedSuccessListener() {
+ @Override
+ public void onSuccess(Void nothing) {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ });
}
@Override
diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
index 45f2b25578..7d2966f493 100644
--- a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
@@ -118,6 +118,9 @@ public class SmsSendJob extends SendJob {
Log.w(TAG, npe);
throw new UndeliverableMessageException(npe2);
}
+ } catch (SecurityException se) {
+ Log.w(TAG, se);
+ throw new UndeliverableMessageException(se);
}
}
diff --git a/src/org/thoughtcrime/securesms/permissions/Permissions.java b/src/org/thoughtcrime/securesms/permissions/Permissions.java
index 127a4a6e00..3876150776 100644
--- a/src/org/thoughtcrime/securesms/permissions/Permissions.java
+++ b/src/org/thoughtcrime/securesms/permissions/Permissions.java
@@ -64,6 +64,8 @@ public class Permissions {
private boolean ifNecesary;
+ private boolean condition = true;
+
PermissionsBuilder(PermissionObject permissionObject) {
this.permissionObject = permissionObject;
}
@@ -78,6 +80,12 @@ public class Permissions {
return this;
}
+ public PermissionsBuilder ifNecessary(boolean condition) {
+ this.ifNecesary = true;
+ this.condition = condition;
+ return this;
+ }
+
public PermissionsBuilder withRationaleDialog(@NonNull String message, @NonNull @DrawableRes int... headers) {
this.rationalDialogHeader = headers;
this.rationaleDialogMessage = message;
@@ -128,7 +136,7 @@ public class Permissions {
PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener,
someGrantedListener, someDeniedListener, somePermanentlyDeniedListener);
- if (ifNecesary && permissionObject.hasAll(requestedPermissions)) {
+ if (ifNecesary && (permissionObject.hasAll(requestedPermissions) || !condition)) {
executePreGrantedPermissionsRequest(request);
} else if (rationaleDialogMessage != null && rationalDialogHeader != null) {
executePermissionsRequestWithRationale(request);