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,7 +1073,7 @@ 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()) { if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
@ -1084,6 +1084,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else { } else {
smsDatabase.markAsDecryptFailed(smsMessageId.get()); 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.
if (e.getCause() != null) { if (e.getCause() != null) {
@ -1100,14 +1101,14 @@ 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()) { if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
@ -1118,7 +1119,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else { } else {
smsDatabase.markAsNoSession(smsMessageId.get()); 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) {