Merge branch 'dev' of https://github.com/oxen-io/session-android into refactor-sending

This commit is contained in:
Ryan ZHAO 2021-03-09 14:46:14 +11:00
commit 70a7182320
31 changed files with 1259 additions and 827 deletions

View File

@ -1,5 +1,4 @@
buildscript { buildscript {
ext.kotlin_version = "1.4.0"
ext.kovenant_version = "3.3.0" ext.kovenant_version = "3.3.0"
repositories { repositories {
@ -124,8 +123,8 @@ dependencies {
implementation "com.google.protobuf:protobuf-java:$protobufVersion" implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion" implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion" implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion"
implementation "com.github.lelloman:android-identicons:v11" implementation "com.github.lelloman:android-identicons:v11"
@ -158,8 +157,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.2' testImplementation 'org.robolectric:shadows-multidex:4.2'
} }
def canonicalVersionCode = 142 def canonicalVersionCode = 143
def canonicalVersionName = "1.7.4" def canonicalVersionName = "1.8.0"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -29,66 +29,18 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
import com.google.firebase.iid.FirebaseInstanceId;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.MessagingConfiguration;
import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.snode.SnodeConfiguration; import org.session.libsession.snode.SnodeConfiguration;
import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
import org.session.libsession.utilities.dynamiclanguage.LocaleParser; import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.session.libsession.utilities.preferences.ProfileKeyUtil; import org.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.jobmanager.DependencyInjector;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller;
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope; import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.util.StreamDetails; import org.session.libsignal.service.api.util.StreamDetails;
import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.internal.push.SignalServiceProtos;
@ -100,6 +52,51 @@ import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.utilities.logging.Log;
import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.jobmanager.DependencyInjector;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller;
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
import org.thoughtcrime.securesms.loki.utilities.FcmUtils;
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -111,6 +108,7 @@ import java.util.Set;
import dagger.ObjectGraph; import dagger.ObjectGraph;
import kotlin.Unit; import kotlin.Unit;
import kotlinx.coroutines.Job;
import network.loki.messenger.BuildConfig; import network.loki.messenger.BuildConfig;
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant; import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
@ -118,7 +116,7 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
/** /**
* Will be called once when the TextSecure process is created. * Will be called once when the TextSecure process is created.
* * <p>
* We're using this as an insertion point to patch up the Android PRNG disaster, * We're using this as an insertion point to patch up the Android PRNG disaster,
* to initialize the job manager, and to check for GCM registration freshness. * to initialize the job manager, and to check for GCM registration freshness.
* *
@ -147,6 +145,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private PublicChatAPI publicChatAPI = null; private PublicChatAPI publicChatAPI = null;
public Broadcaster broadcaster = null; public Broadcaster broadcaster = null;
public SignalCommunicationModule communicationModule; public SignalCommunicationModule communicationModule;
private Job firebaseInstanceIdJob;
private volatile boolean isAppVisible; private volatile boolean isAppVisible;
@ -212,11 +211,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
Log.i(TAG, "App is now visible."); Log.i(TAG, "App is now visible.");
KeyCachingService.onAppForegrounded(this); KeyCachingService.onAppForegrounded(this);
// Loki // Loki
if (poller != null) { poller.setCaughtUp(false); } if (poller != null) {
poller.setCaughtUp(false);
}
startPollingIfNeeded(); startPollingIfNeeded();
publicChatManager.markAllAsNotCaughtUp(); publicChatManager.markAllAsNotCaughtUp();
publicChatManager.startPollersIfNeeded(); publicChatManager.startPollersIfNeeded();
MultiDeviceProtocol.syncConfigurationIfNeeded(this);
} }
@Override @Override
@ -226,9 +226,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
KeyCachingService.onAppBackgrounded(this); KeyCachingService.onAppBackgrounded(this);
messageNotifier.setVisibleThread(-1); messageNotifier.setVisibleThread(-1);
// Loki // Loki
if (poller != null) { poller.stopIfNeeded(); } if (poller != null) {
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); } poller.stopIfNeeded();
if (publicChatManager != null) { publicChatManager.stopPollers(); } }
if (closedGroupPoller != null) {
closedGroupPoller.stopIfNeeded();
}
if (publicChatManager != null) {
publicChatManager.stopPollers();
}
} }
@Override @Override
@ -264,9 +270,13 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
return typingStatusSender; return typingStatusSender;
} }
public ReadReceiptManager getReadReceiptManager() { return readReceiptManager; } public ReadReceiptManager getReadReceiptManager() {
return readReceiptManager;
}
public ProfileManager getProfileManager() { return profileManager; } public ProfileManager getProfileManager() {
return profileManager;
}
public boolean isAppVisible() { public boolean isAppVisible() {
return isAppVisible; return isAppVisible;
@ -277,10 +287,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
} }
// Loki // Loki
public @Nullable PublicChatAPI getPublicChatAPI() { public @Nullable
if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { return publicChatAPI; } PublicChatAPI getPublicChatAPI() {
if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) {
return publicChatAPI;
}
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey== null) { return publicChatAPI; } if (userPublicKey == null) {
return publicChatAPI;
}
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
@ -416,12 +431,15 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base))); super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
} }
private static class ProviderInitializationException extends RuntimeException { } private static class ProviderInitializationException extends RuntimeException {
}
// region Loki // region Loki
public boolean setUpStorageAPIIfNeeded() { public boolean setUpStorageAPIIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null || !IdentityKeyUtil.hasIdentityKey(this)) { return false; } if (userPublicKey == null || !IdentityKeyUtil.hasIdentityKey(this)) {
return false;
}
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB); FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
@ -429,21 +447,25 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
return true; return true;
} }
public void registerForFCMIfNeeded(Boolean force) { public void registerForFCMIfNeeded(final Boolean force) {
Context context = this; if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive() && !force) return;
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> { if (force && firebaseInstanceIdJob != null) {
firebaseInstanceIdJob.cancel(null);
}
firebaseInstanceIdJob = FcmUtils.getFcmInstanceId(task->{
if (!task.isSuccessful()) { if (!task.isSuccessful()) {
Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException()); Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException());
return; return Unit.INSTANCE;
} }
String token = task.getResult().getToken(); String token = task.getResult().getToken();
String userPublicKey = TextSecurePreferences.getLocalNumber(context); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return; if (userPublicKey == null) return Unit.INSTANCE;
if (TextSecurePreferences.isUsingFCM(this)) { if (TextSecurePreferences.isUsingFCM(this)) {
LokiPushNotificationManager.register(token, userPublicKey, context, force); LokiPushNotificationManager.register(token, userPublicKey, this, force);
} else { } else {
LokiPushNotificationManager.unregister(token, context); LokiPushNotificationManager.unregister(token, this);
} }
return Unit.INSTANCE;
}); });
} }
@ -471,14 +493,24 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void startPollingIfNeeded() { public void startPollingIfNeeded() {
setUpPollingIfNeeded(); setUpPollingIfNeeded();
if (poller != null) { poller.startIfNeeded(); } if (poller != null) {
if (closedGroupPoller != null) { closedGroupPoller.startIfNeeded(); } poller.startIfNeeded();
}
if (closedGroupPoller != null) {
closedGroupPoller.startIfNeeded();
}
} }
public void stopPolling() { public void stopPolling() {
if (poller != null) { poller.stopIfNeeded(); } if (poller != null) {
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); } poller.stopIfNeeded();
if (publicChatManager != null) { publicChatManager.stopPollers(); } }
if (closedGroupPoller != null) {
closedGroupPoller.stopIfNeeded();
}
if (publicChatManager != null) {
publicChatManager.stopPollers();
}
} }
private void resubmitProfilePictureIfNeeded() { private void resubmitProfilePictureIfNeeded() {
@ -513,7 +545,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
} catch (Exception e) { } catch (Exception e) {
// Do nothing // Do nothing
} }
if (publicChatAPI == null) { return; } if (publicChatAPI == null) {
return;
}
byte[] profileKey = ProfileKeyUtil.getProfileKey(this); byte[] profileKey = ProfileKeyUtil.getProfileKey(this);
String url = TextSecurePreferences.getProfilePictureURL(this); String url = TextSecurePreferences.getProfilePictureURL(this);
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers(); Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers();
@ -530,6 +564,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (token != null && !token.isEmpty()) { if (token != null && !token.isEmpty()) {
LokiPushNotificationManager.unregister(token, this); LokiPushNotificationManager.unregister(token, this);
} }
if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) {
firebaseInstanceIdJob.cancel(null);
}
String displayName = TextSecurePreferences.getProfileName(this); String displayName = TextSecurePreferences.getProfileName(this);
boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this); boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this);
TextSecurePreferences.clearAll(this); TextSecurePreferences.clearAll(this);

View File

@ -249,7 +249,14 @@ public class RecipientDatabase extends Database {
recipient.resolve().setProfileAvatar(profileAvatar); recipient.resolve().setProfileAvatar(profileAvatar);
} }
public void setProfileSharing(@NonNull Recipient recipient, @SuppressWarnings("SameParameterValue") boolean enabled) { public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(SYSTEM_DISPLAY_NAME, profileName);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileName(profileName);
}
public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) {
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0); contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues); updateOrInsert(recipient.getAddress(), contentValues);

View File

@ -22,6 +22,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.MergeCursor; import android.database.MergeCursor;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -30,6 +31,17 @@ import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabase;
import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.GroupRecord;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSettings;
import org.session.libsession.utilities.DelimiterUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.Pair;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@ -37,32 +49,16 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.threads.GroupRecord;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSettings;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.DelimiterUtil;
import org.session.libsignal.libsignal.util.Pair;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.io.Closeable; import java.io.Closeable;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import nl.komponents.kovenant.combine.Tuple2;
public class ThreadDatabase extends Database { public class ThreadDatabase extends Database {
private static final String TAG = ThreadDatabase.class.getSimpleName(); private static final String TAG = ThreadDatabase.class.getSimpleName();
@ -507,10 +503,16 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
public void notifyUpdatedFromConfig() {
notifyConversationListListeners();
}
public boolean update(long threadId, boolean unarchive) { public boolean update(long threadId, boolean unarchive) {
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context); MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
long count = mmsSmsDatabase.getConversationCount(threadId); long count = mmsSmsDatabase.getConversationCount(threadId);
if (count == 0) { if (count == 0) {
deleteThread(threadId); deleteThread(threadId);
notifyConversationListListeners(); notifyConversationListListeners();

View File

@ -27,10 +27,10 @@ import android.text.style.StyleSpan;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase;
import network.loki.messenger.R; import network.loki.messenger.R;
@ -116,6 +116,8 @@ public class ThreadRecord extends DisplayRecord {
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified)); return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified));
} else if (SmsDatabase.Types.isIdentityDefault(type)) { } else if (SmsDatabase.Types.isIdentityDefault(type)) {
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified)); return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified));
} else if (getCount() == 0) {
return new SpannableString(context.getString(R.string.ThreadRecord_empty_message));
} else { } else {
if (TextUtils.isEmpty(getBody())) { if (TextUtils.isEmpty(getBody())) {
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message))); return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message)));

View File

@ -2,29 +2,30 @@ package org.thoughtcrime.securesms.jobs;
import android.app.Application; import android.app.Application;
import androidx.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.messaging.jobs.Data; import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.thoughtcrime.securesms.jobmanager.Job; import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.session.libsignal.service.api.SignalServiceMessageReceiver; import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
@ -110,6 +111,9 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
if (downloadDestination != null) downloadDestination.delete(); if (downloadDestination != null) downloadDestination.delete();
} }
if (recipient.isLocalNumber()) {
TextSecurePreferences.setProfileAvatarId(context, new SecureRandom().nextInt());
}
database.setProfileAvatar(recipient, profileAvatar); database.setProfileAvatar(recipient, profileAvatar);
} }

View File

@ -22,31 +22,41 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.ProfilePictureModifiedEvent
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.Util
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.session.libsession.utilities.GroupUtil
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.dialogs.*
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.loki.views.ConversationView import org.thoughtcrime.securesms.loki.views.ConversationView
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetViewDelegate
import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate import org.thoughtcrime.securesms.loki.views.SeedReminderViewDelegate
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.Util
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.loki.dialogs.*
import java.io.IOException import java.io.IOException
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate { class HomeActivity : PassphraseRequiredActionBarActivity(),
ConversationClickListener,
SeedReminderViewDelegate,
NewConversationButtonSetViewDelegate {
private lateinit var glide: GlideRequests private lateinit var glide: GlideRequests
private var broadcastReceiver: BroadcastReceiver? = null private var broadcastReceiver: BroadcastReceiver? = null
@ -57,8 +67,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
} }
// region Lifecycle // region Lifecycle
constructor() : super()
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
// Double check that the long poller is up // Double check that the long poller is up
@ -71,9 +79,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
glide = GlideApp.with(this) glide = GlideApp.with(this)
// Set up toolbar buttons // Set up toolbar buttons
profileButton.glide = glide profileButton.glide = glide
profileButton.publicKey = publicKey updateProfileButton()
profileButton.displayName = TextSecurePreferences.getProfileName(this)
profileButton.update()
profileButton.setOnClickListener { openSettings() } profileButton.setOnClickListener { openSettings() }
pathStatusViewContainer.disableClipping() pathStatusViewContainer.disableClipping()
pathStatusViewContainer.setOnClickListener { showPath() } pathStatusViewContainer.setOnClickListener { showPath() }
@ -149,11 +155,19 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
} }
this.broadcastReceiver = broadcastReceiver this.broadcastReceiver = broadcastReceiver
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged")) LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
lifecycleScope.launch {
// update things based on TextSecurePrefs (profile info etc)
TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect {
updateProfileButton()
}
}
EventBus.getDefault().register(this@HomeActivity)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (TextSecurePreferences.getLocalNumber(this) == null) { return; } // This can be the case after a secondary device is auto-cleared if (TextSecurePreferences.getLocalNumber(this) == null) {
return; } // This can be the case after a secondary device is auto-cleared
profileButton.recycle() // clear cached image before update tje profilePictureView profileButton.recycle() // clear cached image before update tje profilePictureView
profileButton.update() profileButton.update()
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this) val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
@ -162,16 +176,25 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
} }
showKeyPairMigrationSheetIfNeeded() showKeyPairMigrationSheetIfNeeded()
showKeyPairMigrationSuccessSheetIfNeeded() showKeyPairMigrationSuccessSheetIfNeeded()
if (TextSecurePreferences.getConfigurationMessageSynced(this)) {
lifecycleScope.launch(Dispatchers.IO) {
MultiDeviceProtocol.syncConfigurationIfNeeded(this@HomeActivity)
}
}
} }
private fun showKeyPairMigrationSheetIfNeeded() { private fun showKeyPairMigrationSheetIfNeeded() {
if (KeyPairUtilities.hasV2KeyPair(this)) { return } if (KeyPairUtilities.hasV2KeyPair(this)) {
return
}
val keyPairMigrationSheet = KeyPairMigrationBottomSheet() val keyPairMigrationSheet = KeyPairMigrationBottomSheet()
keyPairMigrationSheet.show(supportFragmentManager, keyPairMigrationSheet.tag) keyPairMigrationSheet.show(supportFragmentManager, keyPairMigrationSheet.tag)
} }
private fun showKeyPairMigrationSuccessSheetIfNeeded() { private fun showKeyPairMigrationSuccessSheetIfNeeded() {
if (!KeyPairUtilities.hasV2KeyPair(this) || !TextSecurePreferences.getIsMigratingKeyPair(this)) { return } if (!KeyPairUtilities.hasV2KeyPair(this) || !TextSecurePreferences.getIsMigratingKeyPair(this)) {
return
}
val keyPairMigrationSuccessSheet = KeyPairMigrationSuccessBottomSheet() val keyPairMigrationSuccessSheet = KeyPairMigrationSuccessBottomSheet()
keyPairMigrationSuccessSheet.show(supportFragmentManager, keyPairMigrationSuccessSheet.tag) keyPairMigrationSuccessSheet.show(supportFragmentManager, keyPairMigrationSuccessSheet.tag)
TextSecurePreferences.setIsMigratingKeyPair(this, false) TextSecurePreferences.setIsMigratingKeyPair(this, false)
@ -190,6 +213,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver) LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
} }
super.onDestroy() super.onDestroy()
EventBus.getDefault().unregister(this)
} }
// endregion // endregion
@ -198,6 +222,20 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val threadCount = (recyclerView.adapter as HomeAdapter).itemCount val threadCount = (recyclerView.adapter as HomeAdapter).itemCount
emptyStateContainer.visibility = if (threadCount == 0) View.VISIBLE else View.GONE emptyStateContainer.visibility = if (threadCount == 0) View.VISIBLE else View.GONE
} }
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUpdateProfileEvent(event: ProfilePictureModifiedEvent) {
if (event.recipient.isLocalNumber) {
updateProfileButton()
}
}
private fun updateProfileButton() {
profileButton.publicKey = publicKey
profileButton.displayName = TextSecurePreferences.getProfileName(this)
profileButton.recycle()
profileButton.update()
}
// endregion // endregion
// region Interaction // region Interaction
@ -291,7 +329,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
} }
val dialog = AlertDialog.Builder(this) val dialog = AlertDialog.Builder(this)
dialog.setMessage(dialogMessage) dialog.setMessage(dialogMessage)
dialog.setPositiveButton(R.string.yes) { _, _ -> lifecycleScope.launch(Dispatchers.Main) { dialog.setPositiveButton(R.string.yes) { _, _ ->
lifecycleScope.launch(Dispatchers.Main) {
val context = this@HomeActivity as Context val context = this@HomeActivity as Context
// Send a leave group message if this is an active closed group // Send a leave group message if this is an active closed group
@ -336,7 +375,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
// Notify the user // Notify the user
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show() Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show()
}} }
}
dialog.setNegativeButton(R.string.no) { _, _ -> dialog.setNegativeButton(R.string.no) { _, _ ->
// Do nothing // Do nothing
} }
@ -356,7 +396,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
private fun openSettings() { private fun openSettings() {
val intent = Intent(this, SettingsActivity::class.java) val intent = Intent(this, SettingsActivity::class.java)
show(intent) show(intent, isForResult = true)
} }
private fun showPath() { private fun showPath() {

View File

@ -23,19 +23,14 @@ class LandingActivity : BaseActionBarActivity() {
setContentView(R.layout.activity_landing) setContentView(R.layout.activity_landing)
setUpActionBarSessionLogo(true) setUpActionBarSessionLogo(true)
findViewById<FakeChatView>(R.id.fakeChatView).startAnimating() findViewById<FakeChatView>(R.id.fakeChatView).startAnimating()
findViewById<View>(R.id.registerButton).setOnClickListener { register() } findViewById<View>(R.id.registerButton).setOnClickListener { register() }
findViewById<View>(R.id.restoreButton).setOnClickListener { restoreFromRecoveryPhrase() } findViewById<View>(R.id.restoreButton).setOnClickListener { restore() }
findViewById<View>(R.id.restoreBackupButton).setOnClickListener { restoreFromBackup() } findViewById<View>(R.id.linkButton).setOnClickListener { link() }
// Setup essentials for a new user.
IdentityKeyUtil.generateIdentityKeyPair(this) IdentityKeyUtil.generateIdentityKeyPair(this)
TextSecurePreferences.setLastExperienceVersionCode(this, Util.getCanonicalVersionCode()) TextSecurePreferences.setLastExperienceVersionCode(this, Util.getCanonicalVersionCode())
TextSecurePreferences.setPasswordDisabled(this, true) TextSecurePreferences.setPasswordDisabled(this, true)
TextSecurePreferences.setReadReceiptsEnabled(this, true) TextSecurePreferences.setReadReceiptsEnabled(this, true)
TextSecurePreferences.setTypingIndicatorsEnabled(this, true) TextSecurePreferences.setTypingIndicatorsEnabled(this, true)
// AC: This is a temporary workaround to trick the old code that the screen is unlocked. // AC: This is a temporary workaround to trick the old code that the screen is unlocked.
KeyCachingService.setMasterSecret(applicationContext, Object()) KeyCachingService.setMasterSecret(applicationContext, Object())
} }
@ -45,21 +40,13 @@ class LandingActivity : BaseActionBarActivity() {
push(intent) push(intent)
} }
private fun restoreFromRecoveryPhrase() { private fun restore() {
val intent = Intent(this, RecoveryPhraseRestoreActivity::class.java) val intent = Intent(this, RecoveryPhraseRestoreActivity::class.java)
push(intent) push(intent)
} }
private fun restoreFromBackup() { private fun link() {
val intent = Intent(this, BackupRestoreActivity::class.java) val intent = Intent(this, LinkDeviceActivity::class.java)
push(intent) push(intent)
} }
private fun reset() {
IdentityKeyUtil.delete(this, IdentityKeyUtil.LOKI_SEED)
TextSecurePreferences.removeLocalNumber(this)
TextSecurePreferences.setHasSeenWelcomeScreen(this, false)
val application = ApplicationContext.getInstance(this)
application.stopPolling()
}
} }

View File

@ -0,0 +1,212 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.InputType
import android.view.*
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_create_private_chat.*
import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout
import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager
import kotlinx.android.synthetic.main.activity_link_device.*
import kotlinx.android.synthetic.main.conversation_activity.*
import kotlinx.android.synthetic.main.fragment_recovery_phrase.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val adapter = LinkDeviceActivityAdapter(this)
private var restoreJob: Job? = null
override fun onBackPressed() {
if (restoreJob?.isActive == true) return // don't allow going back with pending job
super.onBackPressed()
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
// Set the registration sync variables
TextSecurePreferences.apply {
setHasViewedSeed(this@LinkDeviceActivity, true)
setConfigurationMessageSynced(this@LinkDeviceActivity, false)
setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
setLastProfileUpdateTime(this@LinkDeviceActivity, 0)
}
// registration variables are synced
setContentView(R.layout.activity_link_device)
viewPager.adapter = adapter
tabLayout.setupWithViewPager(viewPager)
}
// endregion
// region Interaction
override fun handleQRCodeScanned(mnemonic: String) {
try {
val seed = Hex.fromStringCondensed(mnemonic)
continueWithSeed(seed)
} catch (e: Exception) {
Log.e("Loki","Error getting seed from QR code", e)
Toast.makeText(this, "An error occurred.", Toast.LENGTH_LONG).show()
}
}
fun continueWithMnemonic(mnemonic: String) {
val loadFileContents: (String) -> String = { fileName ->
MnemonicUtilities.loadFileContents(this, fileName)
}
try {
val hexEncodedSeed = MnemonicCodec(loadFileContents).decode(mnemonic)
val seed = Hex.fromStringCondensed(hexEncodedSeed)
continueWithSeed(seed)
} catch (error: Exception) {
val message = if (error is MnemonicCodec.DecodingError) {
error.description
} else {
"An error occurred."
}
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
}
private fun continueWithSeed(seed: ByteArray) {
// only have one sync job running at a time (prevent QR from trying to spawn a new job)
if (restoreJob?.isActive == true) return
restoreJob = lifecycleScope.launch {
// RestoreActivity handles seed this way
val keyPairGenerationResult = KeyPairUtilities.generate(seed)
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
KeyPairUtilities.store(this@LinkDeviceActivity, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair)
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this@LinkDeviceActivity, registrationID)
TextSecurePreferences.setLocalNumber(this@LinkDeviceActivity, userHexEncodedPublicKey)
TextSecurePreferences.setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(this@LinkDeviceActivity, true)
loader.isVisible = true
val snackBar = Snackbar.make(containerLayout, R.string.activity_link_device_skip_prompt,Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.registration_activity__skip) { register(true) }
val skipJob = launch {
delay(30_000L)
snackBar.show()
// show a dialog or something saying do you want to skip this bit?
}
// start polling and wait for updated message
ApplicationContext.getInstance(this@LinkDeviceActivity).apply {
setUpStorageAPIIfNeeded()
startPollingIfNeeded()
}
TextSecurePreferences.events.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }.collect {
// handle we've synced
snackBar.dismiss()
skipJob.cancel()
register(false)
}
loader.isVisible = false
}
}
private fun register(skipped: Boolean) {
restoreJob?.cancel()
loader.isVisible = false
TextSecurePreferences.setLastConfigurationSyncTime(this, System.currentTimeMillis())
val intent = Intent(this@LinkDeviceActivity, if (skipped) DisplayNameActivity::class.java else PNModeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
push(intent)
}
// endregion
}
// region Adapter
private class LinkDeviceActivityAdapter(private val activity: LinkDeviceActivity) : FragmentPagerAdapter(activity.supportFragmentManager) {
val recoveryPhraseFragment = RecoveryPhraseFragment()
override fun getCount(): Int {
return 2
}
override fun getItem(index: Int): Fragment {
return when (index) {
0 -> recoveryPhraseFragment
1 -> {
val result = ScanQRCodeWrapperFragment()
result.delegate = activity
result.message = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."
result
}
else -> throw IllegalStateException()
}
}
override fun getPageTitle(index: Int): CharSequence {
return when (index) {
0 -> "Recovery Phrase"
1 -> "Scan QR Code"
else -> throw IllegalStateException()
}
}
}
// endregion
// region Recovery Phrase Fragment
class RecoveryPhraseFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_recovery_phrase, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mnemonicEditText.imeOptions = EditorInfo.IME_ACTION_DONE or 16777216 // Always use incognito keyboard
mnemonicEditText.setRawInputType(InputType.TYPE_CLASS_TEXT)
mnemonicEditText.setOnEditorActionListener { v, actionID, _ ->
if (actionID == EditorInfo.IME_ACTION_DONE) {
val imm = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
handleContinueButtonTapped()
true
} else {
false
}
}
continueButton.setOnClickListener { handleContinueButtonTapped() }
}
private fun handleContinueButtonTapped() {
val mnemonic = mnemonicEditText.text?.trim().toString()
(requireActivity() as LinkDeviceActivity).continueWithMnemonic(mnemonic)
}
}
// endregion

View File

@ -7,15 +7,16 @@ import android.content.Intent
import android.graphics.drawable.TransitionDrawable import android.graphics.drawable.TransitionDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.annotation.DrawableRes
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import kotlinx.android.synthetic.main.activity_display_name.registerButton import kotlinx.android.synthetic.main.activity_display_name.registerButton
import kotlinx.android.synthetic.main.activity_pn_mode.* import kotlinx.android.synthetic.main.activity_pn_mode.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.disableClipping import org.thoughtcrime.securesms.loki.utilities.disableClipping
@ -24,7 +25,6 @@ import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.utilities.show import org.thoughtcrime.securesms.loki.utilities.show
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
import org.thoughtcrime.securesms.loki.views.PNModeView import org.thoughtcrime.securesms.loki.views.PNModeView
import org.session.libsession.utilities.TextSecurePreferences
class PNModeActivity : BaseActionBarActivity() { class PNModeActivity : BaseActionBarActivity() {
private var selectedOptionView: PNModeView? = null private var selectedOptionView: PNModeView? = null
@ -32,7 +32,8 @@ class PNModeActivity : BaseActionBarActivity() {
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo(true)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
setContentView(R.layout.activity_pn_mode) setContentView(R.layout.activity_pn_mode)
contentView.disableClipping() contentView.disableClipping()
fcmOptionView.setOnClickListener { toggleFCM() } fcmOptionView.setOnClickListener { toggleFCM() }
@ -150,10 +151,11 @@ class PNModeActivity : BaseActionBarActivity() {
dialog.create().show() dialog.create().show()
return return
} }
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView)) TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
val application = ApplicationContext.getInstance(this) val application = ApplicationContext.getInstance(this)
application.setUpStorageAPIIfNeeded() application.setUpStorageAPIIfNeeded()
application.startPollingIfNeeded()
application.registerForFCMIfNeeded(true)
val intent = Intent(this, HomeActivity::class.java) val intent = Intent(this, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
show(intent) show(intent)

View File

@ -13,16 +13,16 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_recovery_phrase_restore.* import kotlinx.android.synthetic.main.activity_recovery_phrase_restore.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.session.libsignal.utilities.Hex
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
@ -30,6 +30,14 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
// Set the registration sync variables
TextSecurePreferences.apply {
setHasViewedSeed(this@RecoveryPhraseRestoreActivity, true)
setConfigurationMessageSynced(this@RecoveryPhraseRestoreActivity, false)
setRestorationTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
setLastProfileUpdateTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
}
// registration variables are synced
setContentView(R.layout.activity_recovery_phrase_restore) setContentView(R.layout.activity_recovery_phrase_restore)
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
restoreButton.setOnClickListener { restore() } restoreButton.setOnClickListener { restore() }
@ -69,8 +77,6 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
val registrationID = KeyHelper.generateRegistrationId(false) val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID) TextSecurePreferences.setLocalRegistrationId(this, registrationID)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey) TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
TextSecurePreferences.setRestorationTime(this, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(this, true)
val intent = Intent(this, DisplayNameActivity::class.java) val intent = Intent(this, DisplayNameActivity::class.java)
push(intent) push(intent)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -18,14 +18,15 @@ import android.widget.Toast
import com.goterl.lazycode.lazysodium.utils.KeyPair import com.goterl.lazycode.lazysodium.utils.KeyPair
import kotlinx.android.synthetic.main.activity_register.* import kotlinx.android.synthetic.main.activity_register.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import java.util.*
class RegisterActivity : BaseActionBarActivity() { class RegisterActivity : BaseActionBarActivity() {
private var seed: ByteArray? = null private var seed: ByteArray? = null
@ -38,6 +39,14 @@ class RegisterActivity : BaseActionBarActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register) setContentView(R.layout.activity_register)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
// Set the registration sync variables
TextSecurePreferences.apply {
setHasViewedSeed(this@RegisterActivity, false)
setConfigurationMessageSynced(this@RegisterActivity, true)
setRestorationTime(this@RegisterActivity, 0)
setLastProfileUpdateTime(this@RegisterActivity, System.currentTimeMillis())
}
// registration variables are synced
registerButton.setOnClickListener { register() } registerButton.setOnClickListener { register() }
copyButton.setOnClickListener { copyPublicKey() } copyButton.setOnClickListener { copyPublicKey() }
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy") val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")

View File

@ -27,29 +27,29 @@ import nl.komponents.kovenant.deferred
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.task import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.alwaysUi
import org.session.libsession.messaging.avatars.AvatarHelper
import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.service.api.util.StreamDetails
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.avatar.AvatarSelection
import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsession.messaging.threads.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.dialogs.ChangeUiModeDialog import org.thoughtcrime.securesms.loki.dialogs.ChangeUiModeDialog
import org.thoughtcrime.securesms.loki.dialogs.ClearAllDataDialog import org.thoughtcrime.securesms.loki.dialogs.ClearAllDataDialog
import org.thoughtcrime.securesms.loki.dialogs.SeedDialog import org.thoughtcrime.securesms.loki.dialogs.SeedDialog
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.session.libsession.messaging.avatars.AvatarHelper
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.BitmapUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.api.util.StreamDetails
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.security.SecureRandom import java.security.SecureRandom
@ -68,6 +68,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
return TextSecurePreferences.getLocalNumber(this)!! return TextSecurePreferences.getLocalNumber(this)!!
} }
companion object {
const val updatedProfileResultCode = 1234
}
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
@ -203,6 +207,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
// updating the profile name or picture // updating the profile name or picture
if (profilePicture != null || displayName != null) { if (profilePicture != null || displayName != null) {
task { task {
if (isUpdatingProfilePicture && profilePicture != null) {
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
ApplicationContext.getInstance(this).updateOpenGroupProfilePicturesIfNeeded()
}
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
} }
} else { } else {
@ -212,15 +222,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
if (displayName != null) { if (displayName != null) {
btnGroupNameDisplay.text = displayName btnGroupNameDisplay.text = displayName
} }
displayNameToBeUploaded = null
if (isUpdatingProfilePicture && profilePicture != null) { if (isUpdatingProfilePicture && profilePicture != null) {
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
ApplicationContext.getInstance(this).updateOpenGroupProfilePicturesIfNeeded()
profilePictureView.recycle() // clear cached image before update tje profilePictureView profilePictureView.recycle() // clear cached image before update tje profilePictureView
profilePictureView.update() profilePictureView.update()
} }
displayNameToBeUploaded = null
profilePictureToBeUploaded = null profilePictureToBeUploaded = null
loader.isVisible = false loader.isVisible = false
} }

View File

@ -7,11 +7,17 @@ import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
@ -42,17 +48,29 @@ object MultiDeviceProtocol {
} }
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey))) MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
} }
// TODO: remove this after we migrate to new message receiving pipeline // TODO: remove this after we migrate to new message receiving pipeline
@JvmStatic @JvmStatic
fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) { fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) {
if (TextSecurePreferences.getConfigurationMessageSynced(context)) return synchronized(this) {
val configurationMessage = ConfigurationMessage.fromProto(content) ?: return
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, timestamp)) return
if (senderPublicKey != userPublicKey) return if (senderPublicKey != userPublicKey) return
TextSecurePreferences.setConfigurationMessageSynced(context, true)
TextSecurePreferences.setLastProfileUpdateTime(context, timestamp)
val configurationMessage = ConfigurationMessage.fromProto(content) ?: return
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
val recipientDatabase = DatabaseFactory.getRecipientDatabase(context)
val ourRecipient = Recipient.from(context, Address.fromSerialized(userPublicKey),false)
for (closedGroup in configurationMessage.closedGroups) { for (closedGroup in configurationMessage.closedGroups) {
if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue
@ -74,7 +92,40 @@ object MultiDeviceProtocol {
if (allOpenGroups.contains(openGroup)) continue if (allOpenGroups.contains(openGroup)) continue
OpenGroupUtilities.addGroup(context, openGroup, 1) OpenGroupUtilities.addGroup(context, openGroup, 1)
} }
if (configurationMessage.displayName.isNotEmpty()) {
TextSecurePreferences.setProfileName(context, configurationMessage.displayName)
recipientDatabase.setProfileName(ourRecipient, configurationMessage.displayName)
}
if (configurationMessage.profileKey.isNotEmpty()) {
val profileKey = Base64.encodeBytes(configurationMessage.profileKey)
ProfileKeyUtil.setEncodedProfileKey(context, profileKey)
recipientDatabase.setProfileKey(ourRecipient, configurationMessage.profileKey)
if (!configurationMessage.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != configurationMessage.profilePicture) {
TextSecurePreferences.setProfilePictureURL(context, configurationMessage.profilePicture)
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, configurationMessage.profilePicture))
}
}
for (contact in configurationMessage.contacts) {
val address = Address.fromSerialized(contact.publicKey)
val recipient = Recipient.from(context, address, true)
if (!contact.profilePicture.isNullOrEmpty()) {
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
}
if (contact.profileKey?.isNotEmpty() == true) {
recipientDatabase.setProfileKey(recipient, contact.profileKey)
}
if (contact.name.isNotEmpty()) {
recipientDatabase.setProfileName(recipient, contact.name)
}
recipientDatabase.setProfileSharing(recipient, true)
recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED)
// create Thread if needed
threadDatabase.getOrCreateThreadIdFor(recipient)
}
if (configurationMessage.contacts.isNotEmpty()) {
threadDatabase.notifyUpdatedFromConfig()
}
}
// TODO: handle new configuration message fields or handle in new pipeline // TODO: handle new configuration message fields or handle in new pipeline
TextSecurePreferences.setConfigurationMessageSynced(context, true)
} }
} }

View File

@ -0,0 +1,19 @@
@file:JvmName("FcmUtils")
package org.thoughtcrime.securesms.loki.utilities
import com.google.android.gms.tasks.Task
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.InstanceIdResult
import kotlinx.coroutines.*
fun getFcmInstanceId(body: (Task<InstanceIdResult>)->Unit): Job = MainScope().launch(Dispatchers.IO) {
val task = FirebaseInstanceId.getInstance().instanceId
while (!task.isComplete && isActive) {
// wait for task to complete while we are active
}
if (!isActive) return@launch // don't 'complete' task if we were canceled
withContext(Dispatchers.Main) {
body(task)
}
}

View File

@ -12,12 +12,12 @@ import kotlinx.android.synthetic.main.view_profile_picture.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.avatars.ProfileContactPhoto import org.session.libsession.messaging.avatars.ProfileContactPhoto
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator
import org.thoughtcrime.securesms.mms.GlideRequests
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator
import org.thoughtcrime.securesms.mms.GlideRequests
// TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes? // TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes?
@ -151,7 +151,7 @@ class ProfilePictureView : RelativeLayout {
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0" if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") { && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
glide.clear(imageView) glide.clear(imageView)
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView)
imagesCached.add(publicKey) imagesCached.add(publicKey)
} else { } else {
val sizeInPX = resources.getDimensionPixelSize(sizeResId) val sizeInPX = resources.getDimensionPixelSize(sizeResId)

View File

@ -33,14 +33,20 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import android.text.TextUtils;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.ServiceUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.internal.util.Util;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -50,16 +56,11 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.session.libsession.utilities.ServiceUtil;
import org.thoughtcrime.securesms.util.SpanUtil; import org.thoughtcrime.securesms.util.SpanUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.internal.util.Util;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -247,8 +248,8 @@ public class DefaultMessageNotifier implements MessageNotifier {
telcoCursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread(); telcoCursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread();
pushCursor = DatabaseFactory.getPushDatabase(context).getPending(); pushCursor = DatabaseFactory.getPushDatabase(context).getPending();
if ((telcoCursor == null || telcoCursor.isAfterLast()) && if (((telcoCursor == null || telcoCursor.isAfterLast()) &&
(pushCursor == null || pushCursor.isAfterLast())) (pushCursor == null || pushCursor.isAfterLast())) || !TextSecurePreferences.hasSeenWelcomeScreen(context))
{ {
cancelActiveNotifications(context); cancelActiveNotifications(context);
updateBadge(context, 0); updateBadge(context, 0);

View File

@ -1,12 +1,7 @@
package org.thoughtcrime.securesms.preferences; package org.thoughtcrime.securesms.preferences;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -14,34 +9,16 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.EditTextPreference; import androidx.preference.EditTextPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.greenrobot.eventbus.EventBus; import org.session.libsession.utilities.TextSecurePreferences;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.backup.BackupDialog;
import org.thoughtcrime.securesms.backup.BackupEvent;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.preferences.widgets.ProgressPreference;
import org.thoughtcrime.securesms.util.BackupDirSelector;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Trimmer; import org.thoughtcrime.securesms.util.Trimmer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import network.loki.messenger.R; import network.loki.messenger.R;
public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment { public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
private static final String TAG = ChatsPreferenceFragment.class.getSimpleName(); private static final String TAG = ChatsPreferenceFragment.class.getSimpleName();
private BackupDirSelector backupDirSelector;
@Override @Override
public void onCreate(Bundle paramBundle) { public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle); super.onCreate(paramBundle);
@ -51,14 +28,6 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH) findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH)
.setOnPreferenceChangeListener(new TrimLengthValidationListener()); .setOnPreferenceChangeListener(new TrimLengthValidationListener());
findPreference(TextSecurePreferences.BACKUP_ENABLED)
.setOnPreferenceClickListener(new BackupClickListener());
findPreference(TextSecurePreferences.BACKUP_NOW)
.setOnPreferenceClickListener(new BackupCreateListener());
backupDirSelector = new BackupDirSelector(this);
EventBus.getDefault().register(this);
} }
@Override @Override
@ -69,13 +38,11 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
setBackupSummary();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
EventBus.getDefault().unregister(this);
} }
@Override @Override
@ -83,79 +50,6 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
} }
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
backupDirSelector.onActivityResult(requestCode, resultCode, data);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(BackupEvent event) {
ProgressPreference preference = findPreference(TextSecurePreferences.BACKUP_NOW);
if (event.getType() == BackupEvent.Type.PROGRESS) {
preference.setEnabled(false);
preference.setSummary(getString(R.string.ChatsPreferenceFragment_in_progress));
preference.setProgress(event.getCount());
} else if (event.getType() == BackupEvent.Type.FINISHED) {
preference.setEnabled(true);
preference.setProgressVisible(false);
setBackupSummary();
if (event.getException() != null) {
Toast.makeText(
getActivity(),
getString(R.string.preferences_chats__backup_export_error),
Toast.LENGTH_LONG)
.show();
}
}
}
private void setBackupSummary() {
findPreference(TextSecurePreferences.BACKUP_NOW)
.setSummary(String.format(getString(R.string.ChatsPreferenceFragment_last_backup_s),
BackupUtil.getLastBackupTimeString(getContext(), Locale.getDefault())));
}
private CharSequence getSummaryForMediaPreference(Set<String> allowedNetworks) {
String[] keys = getResources().getStringArray(R.array.pref_media_download_entries);
String[] values = getResources().getStringArray(R.array.pref_media_download_values);
List<String> outValues = new ArrayList<>(allowedNetworks.size());
for (int i=0; i < keys.length; i++) {
if (allowedNetworks.contains(keys[i])) outValues.add(values[i]);
}
return outValues.isEmpty() ? getResources().getString(R.string.preferences__none)
: TextUtils.join(", ", outValues);
}
private class BackupClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (!((SwitchPreferenceCompat) preference).isChecked()) {
BackupDialog.showEnableBackupDialog(getActivity(), (SwitchPreferenceCompat)preference, backupDirSelector);
} else {
BackupDialog.showDisableBackupDialog(getActivity(), (SwitchPreferenceCompat)preference);
}
return true;
}
}
private class BackupCreateListener implements Preference.OnPreferenceClickListener {
@SuppressLint("StaticFieldLeak")
@Override
public boolean onPreferenceClick(Preference preference) {
Log.i(TAG, "Queuing backup...");
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new LocalBackupJob());
return true;
}
}
private class TrimNowClickListener implements Preference.OnPreferenceClickListener { private class TrimNowClickListener implements Preference.OnPreferenceClickListener {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
@ -179,19 +73,10 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
} }
} }
private class MediaDownloadChangeListener implements Preference.OnPreferenceChangeListener {
@SuppressWarnings("unchecked")
@Override public boolean onPreferenceChange(Preference preference, Object newValue) {
Log.i(TAG, "onPreferenceChange");
preference.setSummary(getSummaryForMediaPreference((Set<String>)newValue));
return true;
}
}
private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener { private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener {
public TrimLengthValidationListener() { public TrimLengthValidationListener() {
EditTextPreference preference = (EditTextPreference)findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH); EditTextPreference preference = findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH);
onPreferenceChange(preference, preference.getText()); onPreferenceChange(preference, preference.getText());
} }
@ -218,7 +103,4 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
} }
} }
public static CharSequence getSummary(Context context) {
return null;
}
} }

View File

@ -49,27 +49,16 @@
android:layout_marginRight="@dimen/massive_spacing" android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/activity_landing_restore_button_title" /> android:text="@string/activity_landing_restore_button_title" />
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/restoreBackupButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/activity_landing_restore_backup_button_title" />
<Button <Button
android:id="@+id/linkButton" android:id="@+id/linkButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset" android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing" android:layout_marginRight="@dimen/massive_spacing"
android:visibility="invisible"
android:gravity="center" android:gravity="center"
android:background="@color/transparent" android:background="@color/transparent"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="@dimen/medium_font_size" android:textSize="@dimen/medium_font_size"
android:text="@string/activity_landing_link_button_title" /> android:text="Link a Device" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/very_large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Recovery Phrase" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="7dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="To link your device, enter the recovery phrase that was given to you when you signed up." />
<EditText
style="@style/SessionEditText"
android:id="@+id/mnemonicEditText"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:gravity="center_vertical"
android:inputType="textMultiLine"
android:maxLines="2"
android:hint="@string/activity_restore_seed_edit_text_hint" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:id="@+id/continueButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="@string/continue_2" />
</LinearLayout>

View File

@ -49,27 +49,16 @@
android:layout_marginRight="@dimen/massive_spacing" android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/activity_landing_restore_button_title" /> android:text="@string/activity_landing_restore_button_title" />
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/restoreBackupButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/activity_landing_restore_backup_button_title" />
<Button <Button
android:id="@+id/linkButton" android:id="@+id/linkButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/onboarding_button_bottom_offset" android:layout_height="@dimen/onboarding_button_bottom_offset"
android:layout_marginLeft="@dimen/massive_spacing" android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing" android:layout_marginRight="@dimen/massive_spacing"
android:visibility="invisible"
android:gravity="center" android:gravity="center"
android:background="@color/transparent" android:background="@color/transparent"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="@dimen/medium_font_size" android:textSize="@dimen/medium_font_size"
android:text="@string/activity_landing_link_button_title" /> android:text="Link a Device" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/containerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.google.android.material.tabs.TabLayout
style="@style/Widget.Session.TabLayout"
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_bar_height" />
</androidx.viewpager.widget.ViewPager>
<FrameLayout
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#A4000000"
android:visibility="gone">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_centerInParent="true"
app:SpinKit_Color="@android:color/white" />
</RelativeLayout>
</FrameLayout>
</RelativeLayout>

View File

@ -67,14 +67,4 @@
</LinearLayout> </LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/dialog_seed_disclaimer"
android:textColor="@color/text"
android:alpha="0.6"
android:textSize="10sp"
android:textAlignment="center" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Recovery Phrase" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="4dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="@color/text"
android:text="@string/activity_restore_explanation" />
<EditText
style="@style/SmallSessionEditText"
android:id="@+id/mnemonicEditText"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="10dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:gravity="center_vertical"
android:inputType="textMultiLine"
android:maxLines="2"
android:hint="@string/activity_restore_seed_edit_text_hint" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:id="@+id/continueButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="@string/continue_2" />
</LinearLayout>

View File

@ -707,6 +707,7 @@
<string name="ThreadRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string> <string name="ThreadRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
<string name="ThreadRecord_you_marked_verified">You marked verified</string> <string name="ThreadRecord_you_marked_verified">You marked verified</string>
<string name="ThreadRecord_you_marked_unverified">You marked unverified</string> <string name="ThreadRecord_you_marked_unverified">You marked unverified</string>
<string name="ThreadRecord_empty_message">This conversation is empty</string>
<!-- UpdateApkReadyListener --> <!-- UpdateApkReadyListener -->
<string name="UpdateApkReadyListener_Signal_update">Session update</string> <string name="UpdateApkReadyListener_Signal_update">Session update</string>
@ -1881,5 +1882,7 @@
<string name="activity_backup_restore_select_file">Select a file</string> <string name="activity_backup_restore_select_file">Select a file</string>
<string name="activity_backup_restore_explanation_1">Select a backup file and enter the passphrase it was created with.</string> <string name="activity_backup_restore_explanation_1">Select a backup file and enter the passphrase it was created with.</string>
<string name="activity_backup_restore_passphrase">30-digit passphrase</string> <string name="activity_backup_restore_passphrase">30-digit passphrase</string>
<!-- LinkDeviceActivity -->
<string name="activity_link_device_skip_prompt">This is taking a while, would you like to skip?</string>
</resources> </resources>

View File

@ -88,7 +88,7 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:layout="@layout/preference_divider" /> <!-- <PreferenceCategory android:layout="@layout/preference_divider" />
<PreferenceCategory android:key="backup_category" android:title="@string/preferences_chats__backups"> <PreferenceCategory android:key="backup_category" android:title="@string/preferences_chats__backups">
@ -105,6 +105,6 @@
android:dependency="pref_backup_enabled_v3" android:dependency="pref_backup_enabled_v3"
tools:summary="Last backup: 3 days ago" /> tools:summary="Last backup: 3 days ago" />
</PreferenceCategory> </PreferenceCategory> -->
</PreferenceScreen> </PreferenceScreen>

View File

@ -58,6 +58,7 @@ allprojects {
} }
project.ext { project.ext {
kotlin_version = "1.4.31"
androidBuildToolsVersion = '29.0.3' androidBuildToolsVersion = '29.0.3'
androidCompileSdkVersion = 29 // This is also our target SDK. androidCompileSdkVersion = 29 // This is also our target SDK.
androidMinSdkVersion = 21 androidMinSdkVersion = 21

View File

@ -38,7 +38,7 @@ dependencies {
// Remote: // Remote:
implementation 'org.greenrobot:eventbus:3.0.0' implementation 'org.greenrobot:eventbus:3.0.0'
implementation "com.goterl.lazycode:lazysodium-android:4.2.0@aar" implementation "com.goterl.lazycode:lazysodium-android:4.2.0@aar"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'
@ -61,10 +61,10 @@ dependencies {
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "org.threeten:threetenbp:1.3.6" implementation "org.threeten:threetenbp:1.3.6"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
testImplementation "junit:junit:3.8.2" testImplementation "junit:junit:3.8.2"

View File

@ -123,7 +123,8 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
val displayName = configurationProto.displayName val displayName = configurationProto.displayName
val profilePicture = configurationProto.profilePicture val profilePicture = configurationProto.profilePicture
val profileKey = configurationProto.profileKey val profileKey = configurationProto.profileKey
return ConfigurationMessage(closedGroups, openGroups, listOf(), displayName, profilePicture, profileKey.toByteArray()) val contacts = configurationProto.contactsList.mapNotNull { Contact.fromProto(it) }
return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey.toByteArray())
} }
} }

View File

@ -2,14 +2,17 @@ package org.session.libsession.messaging.sending_receiving.notifications
import android.annotation.SuppressLint import android.annotation.SuppressLint
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import okhttp3.* import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
import org.session.libsignal.service.loki.utilities.retryIfNeeded import org.session.libsignal.service.loki.utilities.retryIfNeeded
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.logging.Log
@SuppressLint("StaticFieldLeak")
object PushNotificationAPI { object PushNotificationAPI {
val context = MessagingConfiguration.shared.context val context = MessagingConfiguration.shared.context
val server = "https://live.apns.getsession.org" val server = "https://live.apns.getsession.org"

View File

@ -7,6 +7,9 @@ import android.preference.PreferenceManager.getDefaultSharedPreferences
import android.provider.Settings import android.provider.Settings
import androidx.annotation.ArrayRes import androidx.annotation.ArrayRes
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import org.session.libsession.R import org.session.libsession.R
import org.session.libsession.utilities.preferences.NotificationPrivacyPreference import org.session.libsession.utilities.preferences.NotificationPrivacyPreference
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
@ -16,6 +19,9 @@ import java.util.*
object TextSecurePreferences { object TextSecurePreferences {
private val TAG = TextSecurePreferences::class.simpleName private val TAG = TextSecurePreferences::class.simpleName
private val _events = MutableSharedFlow<String>(0, 64, BufferOverflow.DROP_OLDEST)
val events get() = _events.asSharedFlow()
const val DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase" const val DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase"
const val THEME_PREF = "pref_theme" const val THEME_PREF = "pref_theme"
const val LANGUAGE_PREF = "pref_language" const val LANGUAGE_PREF = "pref_language"
@ -101,7 +107,8 @@ object TextSecurePreferences {
// region Multi Device // region Multi Device
private const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" private const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time"
private const val CONFIGURATION_SYNCED = "pref_configuration_synced" const val CONFIGURATION_SYNCED = "pref_configuration_synced"
private const val LAST_PROFILE_UPDATE_TIME = "pref_last_profile_update_time"
@JvmStatic @JvmStatic
fun getLastConfigurationSyncTime(context: Context): Long { fun getLastConfigurationSyncTime(context: Context): Long {
@ -121,6 +128,7 @@ object TextSecurePreferences {
@JvmStatic @JvmStatic
fun setConfigurationMessageSynced(context: Context, value: Boolean) { fun setConfigurationMessageSynced(context: Context, value: Boolean) {
setBooleanPreference(context, CONFIGURATION_SYNCED, value) setBooleanPreference(context, CONFIGURATION_SYNCED, value)
_events.tryEmit(CONFIGURATION_SYNCED)
} }
@JvmStatic @JvmStatic
@ -321,6 +329,7 @@ object TextSecurePreferences {
@JvmStatic @JvmStatic
fun setProfileName(context: Context, name: String?) { fun setProfileName(context: Context, name: String?) {
setStringPreference(context, PROFILE_NAME_PREF, name) setStringPreference(context, PROFILE_NAME_PREF, name)
_events.tryEmit(PROFILE_NAME_PREF)
} }
@JvmStatic @JvmStatic
@ -746,5 +755,14 @@ object TextSecurePreferences {
fun setIsMigratingKeyPair(context: Context?, newValue: Boolean) { fun setIsMigratingKeyPair(context: Context?, newValue: Boolean) {
setBooleanPreference(context!!, "is_migrating_key_pair", newValue) setBooleanPreference(context!!, "is_migrating_key_pair", newValue)
} }
@JvmStatic
fun setLastProfileUpdateTime(context: Context, profileUpdateTime: Long) {
setLongPreference(context, LAST_PROFILE_UPDATE_TIME, profileUpdateTime)
}
@JvmStatic
fun shouldUpdateProfile(context: Context, profileUpdateTime: Long) =
profileUpdateTime > getLongPreference(context, LAST_PROFILE_UPDATE_TIME, 0)
// endregion // endregion
} }