2014-11-08 19:35:58 +00:00
package org.thoughtcrime.securesms.jobs ;
import android.content.Context ;
2017-08-22 17:44:04 +00:00
import android.support.annotation.NonNull ;
2014-11-08 19:35:58 +00:00
2017-02-18 04:27:11 +00:00
import org.greenrobot.eventbus.EventBus ;
2018-11-22 01:26:06 +00:00
import org.signal.libsignal.metadata.certificate.InvalidCertificateException ;
import org.signal.libsignal.metadata.certificate.SenderCertificate ;
2017-01-06 17:19:58 +00:00
import org.thoughtcrime.securesms.ApplicationContext ;
import org.thoughtcrime.securesms.TextSecureExpiredException ;
2015-10-13 01:25:05 +00:00
import org.thoughtcrime.securesms.attachments.Attachment ;
2018-04-27 00:03:54 +00:00
import org.thoughtcrime.securesms.contactshare.Contact ;
import org.thoughtcrime.securesms.contactshare.ContactModelMapper ;
2017-08-25 19:00:52 +00:00
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil ;
2017-07-26 16:59:15 +00:00
import org.thoughtcrime.securesms.database.Address ;
2014-11-08 19:35:58 +00:00
import org.thoughtcrime.securesms.database.DatabaseFactory ;
2015-11-02 22:32:02 +00:00
import org.thoughtcrime.securesms.events.PartProgressEvent ;
2018-06-18 19:27:04 +00:00
import org.thoughtcrime.securesms.jobmanager.JobParameters ;
2018-08-01 15:09:24 +00:00
import org.thoughtcrime.securesms.logging.Log ;
2018-02-07 22:01:37 +00:00
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader ;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage ;
2014-12-12 09:03:24 +00:00
import org.thoughtcrime.securesms.mms.PartAuthority ;
2014-11-08 19:35:58 +00:00
import org.thoughtcrime.securesms.notifications.MessageNotifier ;
2017-08-01 15:56:00 +00:00
import org.thoughtcrime.securesms.recipients.Recipient ;
2018-02-07 22:01:37 +00:00
import org.thoughtcrime.securesms.util.BitmapDecodingException ;
import org.thoughtcrime.securesms.util.BitmapUtil ;
import org.thoughtcrime.securesms.util.MediaUtil ;
2017-01-06 17:19:58 +00:00
import org.thoughtcrime.securesms.util.TextSecurePreferences ;
2016-03-23 17:34:41 +00:00
import org.whispersystems.libsignal.util.guava.Optional ;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment ;
2018-02-07 22:01:37 +00:00
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage ;
2018-04-27 00:03:54 +00:00
import org.whispersystems.signalservice.api.messages.shared.SharedContact ;
2016-03-23 17:34:41 +00:00
import org.whispersystems.signalservice.api.push.SignalServiceAddress ;
2014-11-08 19:35:58 +00:00
2018-02-07 22:01:37 +00:00
import java.io.ByteArrayInputStream ;
2014-12-12 09:03:24 +00:00
import java.io.IOException ;
import java.io.InputStream ;
2014-11-08 19:35:58 +00:00
import java.util.LinkedList ;
import java.util.List ;
2018-11-09 07:33:37 +00:00
import java.util.UUID ;
2018-06-20 02:22:39 +00:00
import java.util.concurrent.TimeUnit ;
2014-11-08 19:35:58 +00:00
2018-11-27 20:34:42 +00:00
import androidx.work.WorkerParameters ;
2015-01-12 04:27:34 +00:00
public abstract class PushSendJob extends SendJob {
2014-11-08 19:35:58 +00:00
2018-11-22 01:26:06 +00:00
private static final long serialVersionUID = 5906098204770900739L ;
private static final String TAG = PushSendJob . class . getSimpleName ( ) ;
private static final long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit . DAYS . toMillis ( 1 ) ;
2014-11-08 19:35:58 +00:00
2018-11-27 20:34:42 +00:00
protected PushSendJob ( @NonNull Context context , @NonNull WorkerParameters workerParameters ) {
super ( context , workerParameters ) ;
}
2014-11-08 19:35:58 +00:00
protected PushSendJob ( Context context , JobParameters parameters ) {
super ( context , parameters ) ;
}
2018-08-09 14:15:43 +00:00
protected static JobParameters constructParameters ( Address destination ) {
2014-11-08 19:35:58 +00:00
JobParameters . Builder builder = JobParameters . newBuilder ( ) ;
2017-07-26 16:59:15 +00:00
builder . withGroupId ( destination . serialize ( ) ) ;
2018-08-09 14:15:43 +00:00
builder . withNetworkRequirement ( ) ;
2018-06-20 02:22:39 +00:00
builder . withRetryDuration ( TimeUnit . DAYS . toMillis ( 1 ) ) ;
2014-11-08 19:35:58 +00:00
return builder . create ( ) ;
}
2017-01-06 17:19:58 +00:00
@Override
2018-11-15 20:05:08 +00:00
protected final void onSend ( ) throws Exception {
2017-01-06 17:19:58 +00:00
if ( TextSecurePreferences . getSignedPreKeyFailureCount ( context ) > 5 ) {
ApplicationContext . getInstance ( context )
. getJobManager ( )
. add ( new RotateSignedPreKeyJob ( context ) ) ;
throw new TextSecureExpiredException ( " Too many signed prekey rotation failures " ) ;
}
2018-02-02 03:22:48 +00:00
onPushSend ( ) ;
2017-01-06 17:19:58 +00:00
}
2018-06-11 16:37:01 +00:00
@Override
2018-06-20 20:56:05 +00:00
public void onRetry ( ) {
super . onRetry ( ) ;
2018-08-02 13:50:36 +00:00
Log . i ( TAG , " onRetry() " ) ;
2018-06-20 20:56:05 +00:00
2018-08-09 14:15:43 +00:00
if ( getRunAttemptCount ( ) > 1 ) {
2018-08-02 13:50:36 +00:00
Log . i ( TAG , " Scheduling service outage detection job. " ) ;
2018-06-20 20:56:05 +00:00
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new ServiceOutageDetectionJob ( context ) ) ;
}
2018-06-11 16:37:01 +00:00
}
2017-08-22 17:44:04 +00:00
protected Optional < byte [ ] > getProfileKey ( @NonNull Recipient recipient ) {
2017-08-25 19:00:52 +00:00
if ( ! recipient . resolve ( ) . isSystemContact ( ) & & ! recipient . resolve ( ) . isProfileSharing ( ) ) {
return Optional . absent ( ) ;
2017-08-15 01:11:13 +00:00
}
2017-08-25 19:00:52 +00:00
return Optional . of ( ProfileKeyUtil . getProfileKey ( context ) ) ;
2017-08-15 01:11:13 +00:00
}
2017-07-26 16:59:15 +00:00
protected SignalServiceAddress getPushAddress ( Address address ) {
2017-08-07 21:24:53 +00:00
String relay = null ;
2017-07-26 16:59:15 +00:00
return new SignalServiceAddress ( address . toPhoneString ( ) , Optional . fromNullable ( relay ) ) ;
2014-11-08 19:35:58 +00:00
}
2018-01-25 03:17:44 +00:00
protected List < SignalServiceAttachment > getAttachmentsFor ( List < Attachment > parts ) {
2016-03-23 17:34:41 +00:00
List < SignalServiceAttachment > attachments = new LinkedList < > ( ) ;
2014-11-08 19:35:58 +00:00
2015-10-13 01:25:05 +00:00
for ( final Attachment attachment : parts ) {
2018-04-27 00:03:54 +00:00
SignalServiceAttachment converted = getAttachmentFor ( attachment ) ;
if ( converted ! = null ) {
attachments . add ( converted ) ;
2014-11-08 19:35:58 +00:00
}
}
return attachments ;
}
2018-04-27 00:03:54 +00:00
protected SignalServiceAttachment getAttachmentFor ( Attachment attachment ) {
try {
if ( attachment . getDataUri ( ) = = null | | attachment . getSize ( ) = = 0 ) throw new IOException ( " Assertion failed, outgoing attachment has no data! " ) ;
InputStream is = PartAuthority . getAttachmentStream ( context , attachment . getDataUri ( ) ) ;
return SignalServiceAttachment . newStreamBuilder ( )
. withStream ( is )
. withContentType ( attachment . getContentType ( ) )
. withLength ( attachment . getSize ( ) )
. withFileName ( attachment . getFileName ( ) )
. withVoiceNote ( attachment . isVoiceNote ( ) )
. withWidth ( attachment . getWidth ( ) )
. withHeight ( attachment . getHeight ( ) )
2018-11-09 07:33:37 +00:00
. withCaption ( attachment . getCaption ( ) )
2018-04-27 00:03:54 +00:00
. withListener ( ( total , progress ) - > EventBus . getDefault ( ) . postSticky ( new PartProgressEvent ( attachment , total , progress ) ) )
. build ( ) ;
} catch ( IOException ioe ) {
Log . w ( TAG , " Couldn't open attachment " , ioe ) ;
}
return null ;
}
2014-11-08 19:35:58 +00:00
protected void notifyMediaMessageDeliveryFailed ( Context context , long messageId ) {
2017-08-01 15:56:00 +00:00
long threadId = DatabaseFactory . getMmsDatabase ( context ) . getThreadIdForMessage ( messageId ) ;
Recipient recipient = DatabaseFactory . getThreadDatabase ( context ) . getRecipientForThreadId ( threadId ) ;
2014-11-08 19:35:58 +00:00
2017-08-01 15:56:00 +00:00
if ( threadId ! = - 1 & & recipient ! = null ) {
MessageNotifier . notifyMessageDeliveryFailed ( context , recipient , threadId ) ;
2015-04-14 17:01:33 +00:00
}
2014-11-08 19:35:58 +00:00
}
2017-01-06 17:19:58 +00:00
2018-02-07 22:01:37 +00:00
protected Optional < SignalServiceDataMessage . Quote > getQuoteFor ( OutgoingMediaMessage message ) {
if ( message . getOutgoingQuote ( ) = = null ) return Optional . absent ( ) ;
2018-04-02 23:17:32 +00:00
long quoteId = message . getOutgoingQuote ( ) . getId ( ) ;
String quoteBody = message . getOutgoingQuote ( ) . getText ( ) ;
Address quoteAuthor = message . getOutgoingQuote ( ) . getAuthor ( ) ;
List < SignalServiceDataMessage . Quote . QuotedAttachment > quoteAttachments = new LinkedList < > ( ) ;
2018-02-07 22:01:37 +00:00
for ( Attachment attachment : message . getOutgoingQuote ( ) . getAttachments ( ) ) {
2018-04-02 23:17:32 +00:00
BitmapUtil . ScaleResult thumbnailData = null ;
SignalServiceAttachment thumbnail = null ;
2018-02-07 22:01:37 +00:00
try {
if ( MediaUtil . isImageType ( attachment . getContentType ( ) ) & & attachment . getDataUri ( ) ! = null ) {
2018-04-02 23:17:32 +00:00
thumbnailData = BitmapUtil . createScaledBytes ( context , new DecryptableStreamUriLoader . DecryptableUri ( attachment . getDataUri ( ) ) , 100 , 100 , 500 * 1024 ) ;
2018-02-07 22:01:37 +00:00
} else if ( MediaUtil . isVideoType ( attachment . getContentType ( ) ) & & attachment . getThumbnailUri ( ) ! = null ) {
2018-04-02 23:17:32 +00:00
thumbnailData = BitmapUtil . createScaledBytes ( context , new DecryptableStreamUriLoader . DecryptableUri ( attachment . getThumbnailUri ( ) ) , 100 , 100 , 500 * 1024 ) ;
2018-02-07 22:01:37 +00:00
}
2018-04-02 23:17:32 +00:00
if ( thumbnailData ! = null ) {
thumbnail = SignalServiceAttachment . newStreamBuilder ( )
. withContentType ( " image/jpeg " )
. withWidth ( thumbnailData . getWidth ( ) )
. withHeight ( thumbnailData . getHeight ( ) )
. withLength ( thumbnailData . getBitmap ( ) . length )
. withStream ( new ByteArrayInputStream ( thumbnailData . getBitmap ( ) ) )
. build ( ) ;
2018-02-07 22:01:37 +00:00
}
2018-04-02 23:17:32 +00:00
quoteAttachments . add ( new SignalServiceDataMessage . Quote . QuotedAttachment ( attachment . getContentType ( ) ,
attachment . getFileName ( ) ,
thumbnail ) ) ;
2018-02-07 22:01:37 +00:00
} catch ( BitmapDecodingException e ) {
Log . w ( TAG , e ) ;
}
}
return Optional . of ( new SignalServiceDataMessage . Quote ( quoteId , new SignalServiceAddress ( quoteAuthor . serialize ( ) ) , quoteBody , quoteAttachments ) ) ;
}
2018-04-27 00:03:54 +00:00
List < SharedContact > getSharedContactsFor ( OutgoingMediaMessage mediaMessage ) {
List < SharedContact > sharedContacts = new LinkedList < > ( ) ;
for ( Contact contact : mediaMessage . getSharedContacts ( ) ) {
SharedContact . Builder builder = ContactModelMapper . localToRemoteBuilder ( contact ) ;
SharedContact . Avatar avatar = null ;
if ( contact . getAvatar ( ) ! = null & & contact . getAvatar ( ) . getAttachment ( ) ! = null ) {
avatar = SharedContact . Avatar . newBuilder ( ) . withAttachment ( getAttachmentFor ( contact . getAvatarAttachment ( ) ) )
. withProfileFlag ( contact . getAvatar ( ) . isProfile ( ) )
. build ( ) ;
}
builder . setAvatar ( avatar ) ;
sharedContacts . add ( builder . build ( ) ) ;
}
return sharedContacts ;
}
2018-02-07 22:01:37 +00:00
2018-11-22 01:26:06 +00:00
protected void rotateSenderCertificateIfNecessary ( ) throws IOException {
try {
2018-12-03 21:16:29 +00:00
byte [ ] certificateBytes = TextSecurePreferences . getUnidentifiedAccessCertificate ( context ) ;
if ( certificateBytes = = null ) {
throw new InvalidCertificateException ( " No certificate was present. " ) ;
}
SenderCertificate certificate = new SenderCertificate ( certificateBytes ) ;
2018-11-22 01:26:06 +00:00
if ( System . currentTimeMillis ( ) > ( certificate . getExpiration ( ) - CERTIFICATE_EXPIRATION_BUFFER ) ) {
throw new InvalidCertificateException ( " Certificate is expired, or close to it. Expires on: " + certificate . getExpiration ( ) + " , currently: " + System . currentTimeMillis ( ) ) ;
}
Log . d ( TAG , " Certificate is valid. " ) ;
} catch ( InvalidCertificateException e ) {
Log . w ( TAG , " Certificate was invalid at send time. Fetching a new one. " , e ) ;
2018-11-27 20:34:42 +00:00
RotateCertificateJob certificateJob = new RotateCertificateJob ( context ) ;
2018-11-22 01:26:06 +00:00
ApplicationContext . getInstance ( context ) . injectDependencies ( certificateJob ) ;
certificateJob . setContext ( context ) ;
certificateJob . onRun ( ) ;
}
}
2018-02-02 03:22:48 +00:00
protected abstract void onPushSend ( ) throws Exception ;
2014-11-08 19:35:58 +00:00
}