Make sure we have SEND_SMS permission before sending an SMS

Fixes #7246
This commit is contained in:
Moxie Marlinspike 2017-12-05 11:35:15 -08:00
parent 8686691a5a
commit 27e11e9627
4 changed files with 96 additions and 84 deletions

View File

@ -1313,6 +1313,7 @@
<string name="CallNotificationBuilder_connecting">Connecting...</string> <string name="CallNotificationBuilder_connecting">Connecting...</string>
<string name="Permissions_permission_required">Permission required</string> <string name="Permissions_permission_required">Permission required</string>
<string name="Permissions_continue">Continue</string> <string name="Permissions_continue">Continue</string>
<string name="ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms">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\".</string>
<!-- EOF --> <!-- EOF -->

View File

@ -112,7 +112,6 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList; import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent; import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
@ -1663,48 +1662,49 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId, initiating); sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId, initiating);
} }
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating) private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating) {
throws InvalidMessageException
{ OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType);
final SettableFuture<Void> future = new SettableFuture<>();
final Context context = getApplicationContext(); final SettableFuture<Void> future = new SettableFuture<>();
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(recipient, final Context context = getApplicationContext();
slideDeck,
body, final OutgoingMediaMessage outgoingMessage;
System.currentTimeMillis(),
subscriptionId,
expiresIn,
distributionType);
if (isSecureText && !forceSms) { if (isSecureText && !forceSms) {
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage); outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate);
} else {
outgoingMessage = outgoingMessageCandidate;
} }
attachmentManager.clear(glideRequests, false); Permissions.with(this)
composeText.setText(""); .request(Manifest.permission.SEND_SMS)
final long id = fragment.stageOutgoingMessage(outgoingMessage); .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<OutgoingMediaMessage, Void, Long>() { new AsyncTask<Void, Void, Long>() {
@Override @Override
protected Long doInBackground(OutgoingMediaMessage... messages) { protected Long doInBackground(Void... param) {
if (initiating) { if (initiating) {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
} }
return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() { return MessageSender.send(context, masterSecret, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
@Override }
public void onComplete() {
fragment.releaseOutgoingMessage(id);
}
});
}
@Override @Override
protected void onPostExecute(Long result) { protected void onPostExecute(Long result) {
sendComplete(result); sendComplete(result);
future.set(null); future.set(null);
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, outgoingMessage); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
})
.onAnyDenied(() -> future.set(null))
.execute();
return future; 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) private void sendTextMessage(final boolean forceSms, final long expiresIn, final int subscriptionId, final boolean initiatingConversation)
throws InvalidMessageException throws InvalidMessageException
{ {
final Context context = getApplicationContext(); final Context context = getApplicationContext();
final String messageBody = getMessage();
OutgoingTextMessage message; OutgoingTextMessage message;
if (isSecureText && !forceSms) { if (isSecureText && !forceSms) {
message = new OutgoingEncryptedMessage(recipient, getMessage(), expiresIn); message = new OutgoingEncryptedMessage(recipient, messageBody, expiresIn);
} else { } else {
message = new OutgoingTextMessage(recipient, getMessage(), expiresIn, subscriptionId); message = new OutgoingTextMessage(recipient, messageBody, expiresIn, subscriptionId);
} }
this.composeText.setText(""); Permissions.with(this)
final long id = fragment.stageOutgoingMessage(message); .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<OutgoingTextMessage, Void, Long>() { new AsyncTask<OutgoingTextMessage, Void, Long>() {
@Override @Override
protected Long doInBackground(OutgoingTextMessage... messages) { protected Long doInBackground(OutgoingTextMessage... messages) {
if (initiatingConversation) { if (initiatingConversation) {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
} }
return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() { return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
@Override }
public void onComplete() {
fragment.releaseOutgoingMessage(id);
}
});
}
@Override @Override
protected void onPostExecute(Long result) { protected void onPostExecute(Long result) {
sendComplete(result); sendComplete(result);
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
})
.execute();
} }
private void updateToggleButtonState() { private void updateToggleButtonState() {
@ -1830,31 +1835,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
future.addListener(new ListenableFuture.Listener<Pair<Uri, Long>>() { future.addListener(new ListenableFuture.Listener<Pair<Uri, Long>>() {
@Override @Override
public void onSuccess(final @NonNull Pair<Uri, Long> result) { public void onSuccess(final @NonNull Pair<Uri, Long> result) {
try { boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1); long expiresIn = recipient.getExpireMessages() * 1000;
long expiresIn = recipient.getExpireMessages() * 1000; boolean initiating = threadId == -1;
boolean initiating = threadId == -1; AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true);
AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true); SlideDeck slideDeck = new SlideDeck();
SlideDeck slideDeck = new SlideDeck(); slideDeck.addSlide(audioSlide);
slideDeck.addSlide(audioSlide);
sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId, initiating).addListener(new AssertedSuccessListener<Void>() { sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId, initiating).addListener(new AssertedSuccessListener<Void>() {
@Override @Override
public void onSuccess(Void nothing) { public void onSuccess(Void nothing) {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first); PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
return null; return null;
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.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();
}
} }
@Override @Override

View File

@ -118,6 +118,9 @@ public class SmsSendJob extends SendJob {
Log.w(TAG, npe); Log.w(TAG, npe);
throw new UndeliverableMessageException(npe2); throw new UndeliverableMessageException(npe2);
} }
} catch (SecurityException se) {
Log.w(TAG, se);
throw new UndeliverableMessageException(se);
} }
} }

View File

@ -64,6 +64,8 @@ public class Permissions {
private boolean ifNecesary; private boolean ifNecesary;
private boolean condition = true;
PermissionsBuilder(PermissionObject permissionObject) { PermissionsBuilder(PermissionObject permissionObject) {
this.permissionObject = permissionObject; this.permissionObject = permissionObject;
} }
@ -78,6 +80,12 @@ public class Permissions {
return this; 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) { public PermissionsBuilder withRationaleDialog(@NonNull String message, @NonNull @DrawableRes int... headers) {
this.rationalDialogHeader = headers; this.rationalDialogHeader = headers;
this.rationaleDialogMessage = message; this.rationaleDialogMessage = message;
@ -128,7 +136,7 @@ public class Permissions {
PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener, PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener,
someGrantedListener, someDeniedListener, somePermanentlyDeniedListener); someGrantedListener, someDeniedListener, somePermanentlyDeniedListener);
if (ifNecesary && permissionObject.hasAll(requestedPermissions)) { if (ifNecesary && (permissionObject.hasAll(requestedPermissions) || !condition)) {
executePreGrantedPermissionsRequest(request); executePreGrantedPermissionsRequest(request);
} else if (rationaleDialogMessage != null && rationalDialogHeader != null) { } else if (rationaleDialogMessage != null && rationalDialogHeader != null) {
executePermissionsRequestWithRationale(request); executePermissionsRequestWithRationale(request);