Merge remote-tracking branch 'loki/dev' into light-theme

This commit is contained in:
Anton Chekulaev 2020-08-25 15:11:51 +10:00
commit b04028f629
16 changed files with 88 additions and 35 deletions

View File

@ -185,8 +185,8 @@ dependencies {
implementation "com.opencsv:opencsv:4.6" implementation "com.opencsv:opencsv:4.6"
} }
def canonicalVersionCode = 70 def canonicalVersionCode = 72
def canonicalVersionName = "1.4.3" def canonicalVersionName = "1.4.4"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -1853,4 +1853,6 @@
<string name="dialog_seed_disclaimer">*Please note that it is not possible to use the same Session ID on multiple devices simultaneously</string> <string name="dialog_seed_disclaimer">*Please note that it is not possible to use the same Session ID on multiple devices simultaneously</string>
<string name="view_reset_secure_session_done_message">Secure session reset done</string>
</resources> </resources>

View File

@ -358,7 +358,7 @@ public class ConversationFragment extends Fragment
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || if (messageRecord.isGroupAction() || messageRecord.isCallLog() ||
messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate() || messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate() ||
messageRecord.isEndSession() || messageRecord.isIdentityUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate() ||
messageRecord.isIdentityVerified() || messageRecord.isIdentityDefault() || messageRecord.isLokiSessionRestoreSent()) messageRecord.isIdentityVerified() || messageRecord.isIdentityDefault() || messageRecord.isLokiSessionRestoreSent() || messageRecord.isLokiSessionRestoreDone())
{ {
actionMessage = true; actionMessage = true;
} }

View File

@ -114,6 +114,7 @@ public class ConversationUpdateItem extends LinearLayout
else if (messageRecord.isIdentityVerified() || else if (messageRecord.isIdentityVerified() ||
messageRecord.isIdentityDefault()) setIdentityVerifyUpdate(messageRecord); messageRecord.isIdentityDefault()) setIdentityVerifyUpdate(messageRecord);
else if (messageRecord.isLokiSessionRestoreSent()) setTextMessageRecord(messageRecord); else if (messageRecord.isLokiSessionRestoreSent()) setTextMessageRecord(messageRecord);
else if (messageRecord.isLokiSessionRestoreDone()) setTextMessageRecord(messageRecord);
else throw new AssertionError("Neither group nor log nor joined."); else throw new AssertionError("Neither group nor log nor joined.");
if (batchSelected.contains(messageRecord)) setSelected(true); if (batchSelected.contains(messageRecord)) setSelected(true);

View File

@ -84,6 +84,7 @@ public interface MmsSmsColumns {
// Loki // Loki
protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT = 0x01000000; protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT = 0x01000000;
protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_DONE_BIT = 0x00100000;
public static boolean isDraftMessageType(long type) { public static boolean isDraftMessageType(long type) {
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE; return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
@ -237,6 +238,10 @@ public interface MmsSmsColumns {
return (type & ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT) != 0; return (type & ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT) != 0;
} }
public static boolean isLokiSessionRestoreDoneType(long type) {
return (type & ENCRYPTION_LOKI_SESSION_RESTORE_DONE_BIT) != 0;
}
public static boolean isLegacyType(long type) { public static boolean isLegacyType(long type) {
return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0 || return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0 ||
(type & ENCRYPTION_REMOTE_BIT) != 0; (type & ENCRYPTION_REMOTE_BIT) != 0;

View File

@ -255,6 +255,10 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT); updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT);
} }
public void markAsLokiSessionRestorationDone(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_LOKI_SESSION_RESTORE_DONE_BIT);
}
public void markAsLegacyVersion(long id) { public void markAsLegacyVersion(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT); updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
} }

View File

@ -107,6 +107,8 @@ public abstract class DisplayRecord {
public boolean isLokiSessionRestoreSent() { return SmsDatabase.Types.isLokiSessionRestoreSentType(type); } public boolean isLokiSessionRestoreSent() { return SmsDatabase.Types.isLokiSessionRestoreSentType(type); }
public boolean isLokiSessionRestoreDone() { return SmsDatabase.Types.isLokiSessionRestoreDoneType(type); }
public boolean isGroupUpdate() { public boolean isGroupUpdate() {
return SmsDatabase.Types.isGroupUpdate(type); return SmsDatabase.Types.isGroupUpdate(type);
} }

View File

@ -180,7 +180,7 @@ public abstract class MessageRecord extends DisplayRecord {
public boolean isUpdate() { public boolean isUpdate() {
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent(); isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent() || isLokiSessionRestoreDone();
} }
public boolean isMediaPending() { public boolean isMediaPending() {

View File

@ -18,9 +18,10 @@
package org.thoughtcrime.securesms.database.model; package org.thoughtcrime.securesms.database.model;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull;
import android.text.SpannableString; import android.text.SpannableString;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
@ -82,7 +83,9 @@ public class SmsMessageRecord extends MessageRecord {
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) { } else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session)); return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else if (isLokiSessionRestoreSent()) { } else if (isLokiSessionRestoreSent()) {
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, recipient.toShortString())); return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset));
} else if (isLokiSessionRestoreDone()) {
return emphasisAdded(context.getString(R.string.view_reset_secure_session_done_message));
} else if (isEndSession() && isOutgoing()) { } else if (isEndSession() && isOutgoing()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset)); return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset));
} else if (isEndSession()) { } else if (isEndSession()) {

View File

@ -19,13 +19,14 @@ package org.thoughtcrime.securesms.database.model;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -83,7 +84,9 @@ public class ThreadRecord extends DisplayRecord {
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) { } else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session)); return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else if (isLokiSessionRestoreSent()) { } else if (isLokiSessionRestoreSent()) {
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, recipient.toShortString())); return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset));
} else if (isLokiSessionRestoreDone()) {
return emphasisAdded(context.getString(R.string.view_reset_secure_session_done_message));
} else if (SmsDatabase.Types.isEndSessionType(type)) { } else if (SmsDatabase.Types.isEndSessionType(type)) {
return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset)); return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset));
} else if (MmsSmsColumns.Types.isLegacyType(type)) { } else if (MmsSmsColumns.Types.isLegacyType(type)) {

View File

@ -550,7 +550,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{ {
try { try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageMasterDestination(content.getSender()); Recipient recipient = getMessageDestination(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(getMessageMasterDestination(content.getSender()).getAddress(), IncomingMediaMessage mediaMessage = new IncomingMediaMessage(getMessageMasterDestination(content.getSender()).getAddress(),
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, true, message.getExpiresInSeconds() * 1000L, true,
@ -1073,16 +1073,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull Optional<Long> smsMessageId, @NonNull Throwable e) @NonNull Optional<Long> smsMessageId, @NonNull Throwable e)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (SessionMetaProtocol.shouldErrorMessageShow(context, timestamp)) {
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (!smsMessageId.isPresent()) { if (insertResult.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp); smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId());
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
if (insertResult.isPresent()) { }
smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); } else {
messageNotifier.updateNotification(context, insertResult.get().getThreadId()); smsDatabase.markAsDecryptFailed(smsMessageId.get());
} }
} else {
smsDatabase.markAsDecryptFailed(smsMessageId.get());
} }
// FIXME: This is a temporary patch for bad mac issues. At least with this people will be able to message again. We have to figure out the root cause of the issue though. // FIXME: This is a temporary patch for bad mac issues. At least with this people will be able to message again. We have to figure out the root cause of the issue though.
@ -1100,25 +1101,26 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
SessionManagementProtocol.triggerSessionRestorationUI(context, sender); SessionManagementProtocol.triggerSessionRestorationUI(context, sender, timestamp);
} }
private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp, private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (SessionMetaProtocol.shouldErrorMessageShow(context, timestamp)) {
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (!smsMessageId.isPresent()) { if (insertResult.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp); smsDatabase.markAsNoSession(insertResult.get().getMessageId());
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
if (insertResult.isPresent()) { }
smsDatabase.markAsNoSession(insertResult.get().getMessageId()); } else {
messageNotifier.updateNotification(context, insertResult.get().getThreadId()); smsDatabase.markAsNoSession(smsMessageId.get());
} }
} else {
smsDatabase.markAsNoSession(smsMessageId.get());
} }
SessionManagementProtocol.triggerSessionRestorationUI(context, sender); SessionManagementProtocol.triggerSessionRestorationUI(context, sender, timestamp);
} }
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp, private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,

View File

@ -307,7 +307,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
override fun getSessionRequestSentTimestamp(publicKey: String): Long? { override fun getSessionRequestSentTimestamp(publicKey: String): Long? {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
return database.get(sessionRequestSentTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor -> return database.get(sessionRequestSentTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor ->
cursor.getInt(LokiAPIDatabase.timestamp) cursor.getLong(LokiAPIDatabase.timestamp)
}?.toLong() }?.toLong()
} }

View File

@ -12,6 +12,7 @@ import android.view.ViewGroup
import kotlinx.android.synthetic.main.contact_selection_list_fragment.* import kotlinx.android.synthetic.main.contact_selection_list_fragment.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader import org.thoughtcrime.securesms.contacts.ContactsCursorLoader
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
@ -60,6 +61,11 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
return inflater.inflate(R.layout.contact_selection_list_fragment, container, false) return inflater.inflate(R.layout.contact_selection_list_fragment, container, false)
} }
override fun onStop() {
super.onStop()
LoaderManager.getInstance(this).destroyLoader(0)
}
fun setQueryFilter(filter: String?) { fun setQueryFilter(filter: String?) {
cursorFilter = filter cursorFilter = filter
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
@ -93,7 +99,12 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
} }
private fun update(items: List<ContactSelectionListItem>) { private fun update(items: List<ContactSelectionListItem>) {
if (activity?.isDestroyed == true) { return } if (activity?.isDestroyed == true) {
Log.e(ContactSelectionListFragment::class.java.name,
"Received a loader callback after the fragment was detached from the activity.",
IllegalStateException())
return
}
listAdapter.items = items listAdapter.items = items
mainContentContainer.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE mainContentContainer.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE emptyStateContainer.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE

View File

@ -62,7 +62,8 @@ object SessionManagementProtocol {
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val sentTimestamp = apiDB.getSessionRequestSentTimestamp(publicKey) ?: 0 val sentTimestamp = apiDB.getSessionRequestSentTimestamp(publicKey) ?: 0
val processedTimestamp = apiDB.getSessionRequestProcessedTimestamp(publicKey) ?: 0 val processedTimestamp = apiDB.getSessionRequestProcessedTimestamp(publicKey) ?: 0
return timestamp > sentTimestamp && timestamp > processedTimestamp val restorationTimestamp = TextSecurePreferences.getRestorationTime(context)
return timestamp > sentTimestamp && timestamp > processedTimestamp && timestamp > restorationTimestamp
} }
@JvmStatic @JvmStatic
@ -99,10 +100,13 @@ object SessionManagementProtocol {
} }
@JvmStatic @JvmStatic
fun triggerSessionRestorationUI(context: Context, publicKey: String) { fun triggerSessionRestorationUI(context: Context, publicKey: String, errorTimestamp: Long) {
val masterDevicePublicKey = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey val masterDevicePublicKey = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey
val masterDeviceAsRecipient = recipient(context, masterDevicePublicKey) val masterDeviceAsRecipient = recipient(context, masterDevicePublicKey)
if (masterDeviceAsRecipient.isGroupRecipient) { return } if (masterDeviceAsRecipient.isGroupRecipient) { return }
if (TextSecurePreferences.getRestorationTime(context) > errorTimestamp) {
return ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(publicKey)
}
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(masterDeviceAsRecipient) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(masterDeviceAsRecipient)
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, publicKey) DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, publicKey)
} }

View File

@ -30,6 +30,12 @@ object SessionMetaProtocol {
return shouldIgnoreMessage return shouldIgnoreMessage
} }
@JvmStatic
fun shouldErrorMessageShow(context: Context, timestamp: Long): Boolean {
val restorationTimestamp = TextSecurePreferences.getRestorationTime(context)
return timestamp > restorationTimestamp
}
@JvmStatic @JvmStatic
fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) { fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) {
val rawDisplayName = content.senderDisplayName.orNull() ?: return val rawDisplayName = content.senderDisplayName.orNull() ?: return

View File

@ -2,7 +2,10 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
import org.whispersystems.libsignal.loki.SessionResetProtocol import org.whispersystems.libsignal.loki.SessionResetProtocol
import org.whispersystems.libsignal.loki.SessionResetStatus import org.whispersystems.libsignal.loki.SessionResetStatus
import org.whispersystems.libsignal.protocol.PreKeySignalMessage import org.whispersystems.libsignal.protocol.PreKeySignalMessage
@ -22,7 +25,14 @@ class SessionResetImplementation(private val context: Context) : SessionResetPro
val job = NullMessageSendJob(publicKey) val job = NullMessageSendJob(publicKey)
ApplicationContext.getInstance(context).jobManager.add(job) ApplicationContext.getInstance(context).jobManager.add(job)
} }
// TODO: Show session reset succeed message val smsDB = DatabaseFactory.getSmsDatabase(context)
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
val infoMessage = OutgoingTextMessage(recipient, "", 0, 0)
val infoMessageID = smsDB.insertMessageOutbox(threadID, infoMessage, false, System.currentTimeMillis(), null)
if (infoMessageID > -1) {
smsDB.markAsLokiSessionRestorationDone(infoMessageID)
}
} }
override fun validatePreKeySignalMessage(publicKey: String, message: PreKeySignalMessage) { override fun validatePreKeySignalMessage(publicKey: String, message: PreKeySignalMessage) {