diff --git a/build.gradle b/build.gradle index 99f0c5ec3f..541bcfe97b 100644 --- a/build.gradle +++ b/build.gradle @@ -185,8 +185,8 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 72 -def canonicalVersionName = "1.4.4" +def canonicalVersionCode = 73 +def canonicalVersionName = "1.4.5" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index bc105805a7..c3439423ca 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -25,15 +25,15 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcel; -import android.os.Process; import android.provider.OpenableColumns; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.database.Address; @@ -50,7 +50,6 @@ import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ViewUtil; @@ -337,12 +336,13 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity private InputStream openFileUri(Uri uri) throws IOException { FileInputStream fin = new FileInputStream(uri.getPath()); - int owner = FileUtils.getFileDescriptorOwner(fin.getFD()); - - if (owner == -1 || owner == Process.myUid()) { - fin.close(); - throw new IOException("File owned by application"); - } + // TODO: Remove the commented code if there are no issues with reading shared files by October 2020 +// int owner = FileUtils.getFileDescriptorOwner(fin.getFD()); + +// if (owner == -1 || owner == Process.myUid()) { +// fin.close(); +// throw new IOException("File owned by application"); +// } return fin; } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index 0182fde11e..64e7365b1d 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -426,7 +426,9 @@ public class ConversationAdapter @Override public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { MessageRecord messageRecord = getRecordForPositionOrThrow(position); - viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, messageRecord.getDateReceived())); + long timestamp = messageRecord.getDateReceived(); + if (recipient.getAddress().isOpenGroup()) { timestamp = messageRecord.getTimestamp(); } + viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, timestamp)); } public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) { diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 5e9ff400aa..8948ff6123 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -20,11 +20,12 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.text.TextUtils; import android.util.Pair; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import com.google.android.mms.pdu_alt.NotificationInd; @@ -816,7 +817,8 @@ public class MmsDatabase extends MessagingDatabase { private Optional insertMessageInbox(IncomingMediaMessage retrieved, String contentLocation, - long threadId, long mailbox) + long threadId, long mailbox, + long serverTimestamp) throws MmsException { if (threadId == -1 || retrieved.isGroupMessage()) { @@ -839,7 +841,10 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(THREAD_ID, threadId); contentValues.put(CONTENT_LOCATION, contentLocation); contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED); - contentValues.put(DATE_RECEIVED, retrieved.getSentTimeMillis()); // Loki - This is important due to how we handle GIFs + // In open groups messages should be sorted by their server timestamp + long receivedTimestamp = serverTimestamp; + if (serverTimestamp == 0) { receivedTimestamp = retrieved.getSentTimeMillis(); } + contentValues.put(DATE_RECEIVED, receivedTimestamp); // Loki - This is important due to how we handle GIFs contentValues.put(PART_COUNT, retrieved.getAttachments().size()); contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId()); contentValues.put(EXPIRES_IN, retrieved.getExpiresIn()); @@ -893,11 +898,11 @@ public class MmsDatabase extends MessagingDatabase { type |= Types.EXPIRATION_TIMER_UPDATE_BIT; } - return insertMessageInbox(retrieved, contentLocation, threadId, type); + return insertMessageInbox(retrieved, contentLocation, threadId, type, 0); } - public Optional insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId) - throws MmsException + public Optional insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId, long serverTimestamp) + throws MmsException { long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT; @@ -909,7 +914,13 @@ public class MmsDatabase extends MessagingDatabase { type |= Types.EXPIRATION_TIMER_UPDATE_BIT; } - return insertMessageInbox(retrieved, "", threadId, type); + return insertMessageInbox(retrieved, "", threadId, type, 0); + } + + public Optional insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId) + throws MmsException + { + return insertSecureDecryptedMessageInbox(retrieved, threadId, 0); } public Pair insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId) { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 1727d5e86d..ee4527d00b 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -20,10 +20,11 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Pair; +import androidx.annotation.NonNull; + import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; @@ -580,7 +581,7 @@ public class SmsDatabase extends MessagingDatabase { return new Pair<>(messageId, threadId); } - protected Optional insertMessageInbox(IncomingTextMessage message, long type) { + protected Optional insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp) { if (message.isJoined()) { type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE; } else if (message.isPreKeyBundle()) { @@ -625,7 +626,10 @@ public class SmsDatabase extends MessagingDatabase { ContentValues values = new ContentValues(6); values.put(ADDRESS, message.getSender().serialize()); values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId()); - values.put(DATE_RECEIVED, message.getSentTimestampMillis()); // Loki - This is important due to how we handle GIFs + // In open groups messages should be sorted by their server timestamp + long receivedTimestamp = serverTimestamp; + if (serverTimestamp == 0) { receivedTimestamp = message.getSentTimestampMillis(); } + values.put(DATE_RECEIVED, receivedTimestamp); // Loki - This is important due to how we handle GIFs values.put(DATE_SENT, message.getSentTimestampMillis()); values.put(PROTOCOL, message.getProtocol()); values.put(READ, unread ? 0 : 1); @@ -672,7 +676,11 @@ public class SmsDatabase extends MessagingDatabase { } public Optional insertMessageInbox(IncomingTextMessage message) { - return insertMessageInbox(message, Types.BASE_INBOX_TYPE); + return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0); + } + + public Optional insertMessageInbox(IncomingTextMessage message, long serverTimestamp) { + return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp); } public long insertMessageOutbox(long threadId, OutgoingTextMessage message, diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index 2e54b9d65e..effea38c60 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -139,6 +139,9 @@ public abstract class MessageRecord extends DisplayRecord { if (isPush() && getDateSent() < getDateReceived()) { return getDateSent(); } + if (getRecipient().getAddress().isOpenGroup()) { + return getDateSent(); + } return getDateReceived(); } diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index f1c721718c..766e8f20f6 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -52,7 +52,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(1) + .setMaxAttempts(5) .build(), attachmentId, destination); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 402bf50c31..28ec14dfdf 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -755,7 +755,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType { Optional insertResult; try { - insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + if (message.isGroupMessage()) { + insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1, content.getTimestamp()); + } else { + insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + } if (insertResult.isPresent()) { List allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); @@ -952,7 +956,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType { if (textMessage.getMessageBody().length() == 0) { return; } // Insert the message into the database - Optional insertResult = database.insertMessageInbox(textMessage); + Optional insertResult; + if (message.isGroupMessage()) { + insertResult = database.insertMessageInbox(textMessage, content.getTimestamp()); + } else { + insertResult = database.insertMessageInbox(textMessage); + } if (insertResult.isPresent()) { threadId = insertResult.get().getThreadId(); diff --git a/src/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt index f7c864ad29..eba3f552e9 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt @@ -1,17 +1,15 @@ package org.thoughtcrime.securesms.loki.activities -import android.Manifest import android.content.Intent import android.graphics.Bitmap +import android.net.Uri import android.os.Bundle -import android.os.Environment -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentPagerAdapter import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast -import com.tbruyelle.rxpermissions2.RxPermissions +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentPagerAdapter import kotlinx.android.synthetic.main.activity_qr_code.* import kotlinx.android.synthetic.main.fragment_view_my_qr_code.* import network.loki.messenger.R @@ -106,8 +104,8 @@ class ViewMyQRCodeFragment : Fragment() { private val hexEncodedPublicKey: String get() { - val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context!!) - val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context!!) + val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(requireContext()) + val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext()) return masterHexEncodedPublicKey ?: userHexEncodedPublicKey } @@ -127,33 +125,22 @@ class ViewMyQRCodeFragment : Fragment() { } private fun shareQRCode() { - fun proceed() { - val directory = File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_PICTURES) - val fileName = "$hexEncodedPublicKey.png" - val file = File(directory, fileName) - file.createNewFile() - val fos = FileOutputStream(file) - val size = toPx(280, resources) - val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, false, false) - qrCode.compress(Bitmap.CompressFormat.PNG, 100, fos) - fos.flush() - fos.close() - val intent = Intent(Intent.ACTION_SEND) - intent.putExtra(Intent.EXTRA_STREAM, FileProviderUtil.getUriFor(activity!!, file)) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.type = "image/png" - startActivity(Intent.createChooser(intent, resources.getString(R.string.fragment_view_my_qr_code_share_title))) - } - if (RxPermissions(this).isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - proceed() - } else { - @SuppressWarnings("unused") - val unused = RxPermissions(this).request(Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe { isGranted -> - if (isGranted) { - proceed() - } - } - } + val directory = requireContext().externalCacheDir + val fileName = "$hexEncodedPublicKey.png" + val file = File(directory, fileName) + file.createNewFile() + val fos = FileOutputStream(file) + val size = toPx(280, resources) + val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, false, false) + qrCode.compress(Bitmap.CompressFormat.PNG, 100, fos) + fos.flush() + fos.close() + val intent = Intent(Intent.ACTION_SEND) + intent.putExtra(Intent.EXTRA_STREAM, FileProviderUtil.getUriFor(requireActivity(), file)) + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.type = "image/png" + startActivity(Intent.createChooser(intent, resources.getString(R.string.fragment_view_my_qr_code_share_title))) } } // endregion \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt index 7aaf2322f7..28aa5f93f3 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt @@ -166,7 +166,7 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh } val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.senderPublicKey val serviceDataMessage = getDataMessage(message) - val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false, false) + val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false, false) if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } else { diff --git a/src/org/thoughtcrime/securesms/util/ResUtil.java b/src/org/thoughtcrime/securesms/util/ResUtil.java index 555f11e365..f4215de9f0 100644 --- a/src/org/thoughtcrime/securesms/util/ResUtil.java +++ b/src/org/thoughtcrime/securesms/util/ResUtil.java @@ -22,13 +22,14 @@ import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.util.TypedValue; + import androidx.annotation.ArrayRes; import androidx.annotation.AttrRes; import androidx.annotation.DimenRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import android.util.TypedValue; import org.thoughtcrime.securesms.logging.Log; @@ -57,7 +58,7 @@ public class ResUtil { int drawableRes = getDrawableRes(c, attr); if (drawableRes == 0) { Log.e(TAG, "Cannot find a drawable resource associated with the attribute: " + attr, - new Resources.NotFoundException()); + new Resources.NotFoundException()); return null; } return ContextCompat.getDrawable(c, drawableRes);