fix: fcm task was not cancelable and cannot remove listeners

pull/468/head
jubb 4 years ago
parent d631897a3a
commit 1b417362ae

@ -29,27 +29,32 @@ 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.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.SSKEnvironment;
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.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.util.StreamDetails;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.loki.api.Poller;
import org.session.libsignal.service.loki.api.PushNotificationAPI;
import org.session.libsignal.service.loki.api.SnodeAPI;
import org.session.libsignal.service.loki.api.SwarmAPI;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.utilities.logging.Log;
import org.signal.aesgcmprovider.AesGcmProvider; 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.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
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.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
@ -61,7 +66,6 @@ import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.JobManagerFactories; import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob; import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.logging.AndroidLogger; import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.loki.activities.HomeActivity; import org.thoughtcrime.securesms.loki.activities.HomeActivity;
@ -69,11 +73,13 @@ import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller; import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller;
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager; import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
import org.thoughtcrime.securesms.loki.api.PublicChatManager; 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.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol; import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.loki.utilities.Broadcaster; 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.loki.utilities.UiModeUtilities;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
@ -83,22 +89,14 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; 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.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
import org.webrtc.PeerConnectionFactory; import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions; import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager; import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils; import org.webrtc.voiceengine.WebRtcAudioUtils;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.util.StreamDetails;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.loki.api.Poller;
import org.session.libsignal.service.loki.api.PushNotificationAPI;
import org.session.libsignal.service.loki.api.SnodeAPI;
import org.session.libsignal.service.loki.api.SwarmAPI;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -110,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;
@ -117,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.
* *
@ -125,428 +124,465 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
*/ */
public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver { public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver {
public static final String PREFERENCES_NAME = "SecureSMS-Preferences"; public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
private static final String TAG = ApplicationContext.class.getSimpleName(); private static final String TAG = ApplicationContext.class.getSimpleName();
private ExpiringMessageManager expiringMessageManager; private ExpiringMessageManager expiringMessageManager;
private TypingStatusRepository typingStatusRepository; private TypingStatusRepository typingStatusRepository;
private TypingStatusSender typingStatusSender; private TypingStatusSender typingStatusSender;
private JobManager jobManager; private JobManager jobManager;
private ReadReceiptManager readReceiptManager; private ReadReceiptManager readReceiptManager;
private ProfileManager profileManager; private ProfileManager profileManager;
private ObjectGraph objectGraph; private ObjectGraph objectGraph;
private PersistentLogger persistentLogger; private PersistentLogger persistentLogger;
// Loki
public MessageNotifier messageNotifier = null;
public Poller poller = null;
public ClosedGroupPoller closedGroupPoller = null;
public PublicChatManager publicChatManager = null;
private PublicChatAPI publicChatAPI = null;
public Broadcaster broadcaster = null;
public SignalCommunicationModule communicationModule;
private volatile boolean isAppVisible;
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate()");
startKovenant();
initializeSecurityProvider();
initializeLogging();
initializeCrashHandling();
initializeDependencyInjection();
NotificationChannels.create(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki
// ========
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
MessagingConfiguration.Companion.configure(this,
DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this));
if (userPublicKey != null) {
SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
}
PushNotificationAPI.Companion.configureIfNeeded(BuildConfig.DEBUG);
setUpStorageAPIIfNeeded();
resubmitProfilePictureIfNeeded();
publicChatManager = new PublicChatManager(this);
updateOpenGroupProfilePicturesIfNeeded();
if (userPublicKey != null) {
registerForFCMIfNeeded(false);
}
// Set application UI mode (day/night theme) to the user selected one.
UiModeUtilities.setupUiModeToUserSelected(this);
// ========
initializeJobManager();
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
initializeReadReceiptManager();
initializeProfileManager();
initializePeriodicTasks();
initializeWebRtc();
initializeBlobProvider();
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
isAppVisible = true;
Log.i(TAG, "App is now visible.");
KeyCachingService.onAppForegrounded(this);
// Loki // Loki
if (poller != null) { poller.setCaughtUp(false); } public MessageNotifier messageNotifier = null;
startPollingIfNeeded(); public Poller poller = null;
publicChatManager.markAllAsNotCaughtUp(); public ClosedGroupPoller closedGroupPoller = null;
publicChatManager.startPollersIfNeeded(); public PublicChatManager publicChatManager = null;
MultiDeviceProtocol.syncConfigurationIfNeeded(this); private PublicChatAPI publicChatAPI = null;
} public Broadcaster broadcaster = null;
public SignalCommunicationModule communicationModule;
@Override private Job firebaseInstanceIdJob;
public void onStop(@NonNull LifecycleOwner owner) {
isAppVisible = false; private volatile boolean isAppVisible;
Log.i(TAG, "App is no longer visible.");
KeyCachingService.onAppBackgrounded(this); public static ApplicationContext getInstance(Context context) {
messageNotifier.setVisibleThread(-1); return (ApplicationContext) context.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate()");
startKovenant();
initializeSecurityProvider();
initializeLogging();
initializeCrashHandling();
initializeDependencyInjection();
NotificationChannels.create(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki
// ========
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this);
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
MessagingConfiguration.Companion.configure(this,
DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this));
if (userPublicKey != null) {
SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
}
PushNotificationAPI.Companion.configureIfNeeded(BuildConfig.DEBUG);
setUpStorageAPIIfNeeded();
resubmitProfilePictureIfNeeded();
publicChatManager = new PublicChatManager(this);
updateOpenGroupProfilePicturesIfNeeded();
if (userPublicKey != null) {
registerForFCMIfNeeded(false);
}
// Set application UI mode (day/night theme) to the user selected one.
UiModeUtilities.setupUiModeToUserSelected(this);
// ========
initializeJobManager();
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
initializeReadReceiptManager();
initializeProfileManager();
initializePeriodicTasks();
initializeWebRtc();
initializeBlobProvider();
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
isAppVisible = true;
Log.i(TAG, "App is now visible.");
KeyCachingService.onAppForegrounded(this);
// Loki
if (poller != null) {
poller.setCaughtUp(false);
}
startPollingIfNeeded();
publicChatManager.markAllAsNotCaughtUp();
publicChatManager.startPollersIfNeeded();
MultiDeviceProtocol.syncConfigurationIfNeeded(this);
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
isAppVisible = false;
Log.i(TAG, "App is no longer visible.");
KeyCachingService.onAppBackgrounded(this);
messageNotifier.setVisibleThread(-1);
// Loki
if (poller != null) {
poller.stopIfNeeded();
}
if (closedGroupPoller != null) {
closedGroupPoller.stopIfNeeded();
}
if (publicChatManager != null) {
publicChatManager.stopPollers();
}
}
@Override
public void onTerminate() {
stopKovenant(); // Loki
super.onTerminate();
}
@Override
public void injectDependencies(Object object) {
if (object instanceof InjectableType) {
objectGraph.inject(object);
}
}
public void initializeLocaleParser() {
LocaleParser.Companion.configure(new LocaleParseHelper());
}
public JobManager getJobManager() {
return jobManager;
}
public ExpiringMessageManager getExpiringMessageManager() {
return expiringMessageManager;
}
public TypingStatusRepository getTypingStatusRepository() {
return typingStatusRepository;
}
public TypingStatusSender getTypingStatusSender() {
return typingStatusSender;
}
public ReadReceiptManager getReadReceiptManager() {
return readReceiptManager;
}
public ProfileManager getProfileManager() {
return profileManager;
}
public boolean isAppVisible() {
return isAppVisible;
}
public PersistentLogger getPersistentLogger() {
return persistentLogger;
}
// Loki // Loki
if (poller != null) { poller.stopIfNeeded(); } public @Nullable
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); } PublicChatAPI getPublicChatAPI() {
if (publicChatManager != null) { publicChatManager.stopPollers(); } if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) {
} return publicChatAPI;
}
@Override String userPublicKey = TextSecurePreferences.getLocalNumber(this);
public void onTerminate() { if (userPublicKey == null) {
stopKovenant(); // Loki return publicChatAPI;
super.onTerminate(); }
} byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
@Override LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
public void injectDependencies(Object object) { GroupDatabase groupDB = DatabaseFactory.getGroupDatabase(this);
if (object instanceof InjectableType) { publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB, groupDB);
objectGraph.inject(object); return publicChatAPI;
} }
}
private void initializeSecurityProvider() {
public void initializeLocaleParser() { try {
LocaleParser.Companion.configure(new LocaleParseHelper()); Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
} } catch (ClassNotFoundException e) {
Log.e(TAG, "Failed to find AesGcmCipher class");
public JobManager getJobManager() { throw new ProviderInitializationException();
return jobManager; }
}
int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
public ExpiringMessageManager getExpiringMessageManager() { Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
return expiringMessageManager;
} if (aesPosition < 0) {
Log.e(TAG, "Failed to install AesGcmProvider()");
public TypingStatusRepository getTypingStatusRepository() { throw new ProviderInitializationException();
return typingStatusRepository; }
}
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
public TypingStatusSender getTypingStatusSender() { Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
return typingStatusSender;
} if (conscryptPosition < 0) {
Log.w(TAG, "Did not install Conscrypt provider. May already be present.");
public ReadReceiptManager getReadReceiptManager() { return readReceiptManager; } }
}
public ProfileManager getProfileManager() { return profileManager; }
private void initializeLogging() {
public boolean isAppVisible() { persistentLogger = new PersistentLogger(this);
return isAppVisible; Log.initialize(new AndroidLogger(), persistentLogger);
} }
public PersistentLogger getPersistentLogger() { private void initializeCrashHandling() {
return persistentLogger; final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
} Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler));
}
// Loki
public @Nullable PublicChatAPI getPublicChatAPI() { private void initializeJobManager() {
if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { return publicChatAPI; } this.jobManager = new JobManager(this, new JobManager.Configuration.Builder()
String userPublicKey = TextSecurePreferences.getLocalNumber(this); .setDataSerializer(new JsonDataSerializer())
if (userPublicKey== null) { return publicChatAPI; } .setJobFactories(JobManagerFactories.getJobFactories(this))
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); .setConstraintFactories(JobManagerFactories.getConstraintFactories(this))
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); .setConstraintObservers(JobManagerFactories.getConstraintObservers(this))
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); .setJobStorage(new FastJobStorage(DatabaseFactory.getJobDatabase(this)))
GroupDatabase groupDB = DatabaseFactory.getGroupDatabase(this); .setDependencyInjector(this)
publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB, groupDB); .build());
return publicChatAPI; }
}
private void initializeDependencyInjection() {
private void initializeSecurityProvider() { communicationModule = new SignalCommunicationModule(this);
try { this.objectGraph = ObjectGraph.create(communicationModule);
Class.forName("org.signal.aesgcmprovider.AesGcmCipher"); }
} catch (ClassNotFoundException e) {
Log.e(TAG, "Failed to find AesGcmCipher class");
throw new ProviderInitializationException(); private void initializeExpiringMessageManager() {
} this.expiringMessageManager = new ExpiringMessageManager(this);
}
int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
Log.i(TAG, "Installed AesGcmProvider: " + aesPosition); private void initializeTypingStatusRepository() {
this.typingStatusRepository = new TypingStatusRepository();
if (aesPosition < 0) { }
Log.e(TAG, "Failed to install AesGcmProvider()");
throw new ProviderInitializationException(); private void initializeReadReceiptManager() {
} this.readReceiptManager = new ReadReceiptManager();
}
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition); private void initializeProfileManager() {
this.profileManager = new ProfileManager();
if (conscryptPosition < 0) { }
Log.w(TAG, "Did not install Conscrypt provider. May already be present.");
} private void initializeTypingStatusSender() {
} this.typingStatusSender = new TypingStatusSender(this);
}
private void initializeLogging() {
persistentLogger = new PersistentLogger(this); private void initializePeriodicTasks() {
Log.initialize(new AndroidLogger(), persistentLogger); LocalBackupListener.schedule(this);
} BackgroundPollWorker.schedulePeriodic(this); // Loki
private void initializeCrashHandling() { if (BuildConfig.PLAY_STORE_DISABLED) {
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler(); UpdateApkRefreshListener.schedule(this);
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler)); }
} }
private void initializeJobManager() { private void initializeWebRtc() {
this.jobManager = new JobManager(this, new JobManager.Configuration.Builder() try {
.setDataSerializer(new JsonDataSerializer()) Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
.setJobFactories(JobManagerFactories.getJobFactories(this)) add("Pixel");
.setConstraintFactories(JobManagerFactories.getConstraintFactories(this)) add("Pixel XL");
.setConstraintObservers(JobManagerFactories.getConstraintObservers(this)) add("Moto G5");
.setJobStorage(new FastJobStorage(DatabaseFactory.getJobDatabase(this))) add("Moto G (5S) Plus");
.setDependencyInjector(this) add("Moto G4");
.build()); add("TA-1053");
} add("Mi A1");
add("E5823"); // Sony z5 compact
private void initializeDependencyInjection() { add("Redmi Note 5");
communicationModule = new SignalCommunicationModule(this); add("FP2"); // Fairphone FP2
this.objectGraph = ObjectGraph.create(communicationModule); add("MI 5");
} }};
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{
private void initializeExpiringMessageManager() { add("Pixel");
this.expiringMessageManager = new ExpiringMessageManager(this); add("Pixel XL");
} }};
private void initializeTypingStatusRepository() { if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
this.typingStatusRepository = new TypingStatusRepository(); WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
} }
private void initializeReadReceiptManager() { if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
this.readReceiptManager = new ReadReceiptManager(); WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
} }
private void initializeProfileManager() { PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions());
this.profileManager = new ProfileManager(); } catch (UnsatisfiedLinkError e) {
} Log.w(TAG, e);
}
private void initializeTypingStatusSender() { }
this.typingStatusSender = new TypingStatusSender(this);
} private void initializeBlobProvider() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
private void initializePeriodicTasks() { BlobProvider.getInstance().onSessionStart(this);
LocalBackupListener.schedule(this); });
BackgroundPollWorker.schedulePeriodic(this); // Loki }
if (BuildConfig.PLAY_STORE_DISABLED) { @Override
UpdateApkRefreshListener.schedule(this); protected void attachBaseContext(Context base) {
} initializeLocaleParser();
} super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
}
private void initializeWebRtc() {
try { private static class ProviderInitializationException extends RuntimeException {
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{ }
add("Pixel");
add("Pixel XL"); // region Loki
add("Moto G5"); public boolean setUpStorageAPIIfNeeded() {
add("Moto G (5S) Plus"); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
add("Moto G4"); if (userPublicKey == null || !IdentityKeyUtil.hasIdentityKey(this)) {
add("TA-1053"); return false;
add("Mi A1"); }
add("E5823"); // Sony z5 compact byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
add("Redmi Note 5"); LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
add("FP2"); // Fairphone FP2 FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
add("MI 5"); return true;
}}; }
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{ public void registerForFCMIfNeeded(final Boolean force) {
add("Pixel"); if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) return;
add("Pixel XL"); firebaseInstanceIdJob = FcmUtils.getFcmInstanceId(task->{
}}; if (!task.isSuccessful()) {
Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException());
if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) { return Unit.INSTANCE;
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true); }
} String token = task.getResult().getToken();
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) { if (userPublicKey == null) return Unit.INSTANCE;
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true); if (TextSecurePreferences.isUsingFCM(this)) {
} LokiPushNotificationManager.register(token, userPublicKey, this, force);
} else {
PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions()); LokiPushNotificationManager.unregister(token, this);
} catch (UnsatisfiedLinkError e) { }
Log.w(TAG, e); return Unit.INSTANCE;
} });
} }
private void initializeBlobProvider() { private void setUpPollingIfNeeded() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { String userPublicKey = TextSecurePreferences.getLocalNumber(this);
BlobProvider.getInstance().onSessionStart(this); if (userPublicKey == null) return;
}); if (poller != null) {
} SnodeAPI.shared.setUserPublicKey(userPublicKey);
poller.setUserPublicKey(userPublicKey);
@Override return;
protected void attachBaseContext(Context base) { }
initializeLocaleParser(); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base))); Context context = this;
} SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
private static class ProviderInitializationException extends RuntimeException { } poller = new Poller(userPublicKey, apiDB, envelopes -> {
for (SignalServiceProtos.Envelope envelope : envelopes) {
// region Loki new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(envelope), false);
public boolean setUpStorageAPIIfNeeded() { }
String userPublicKey = TextSecurePreferences.getLocalNumber(this); return Unit.INSTANCE;
if (userPublicKey == null || !IdentityKeyUtil.hasIdentityKey(this)) { return false; } });
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); ClosedGroupPoller.Companion.configureIfNeeded(this);
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this); closedGroupPoller = ClosedGroupPoller.Companion.getShared();
FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB); }
return true;
} public void startPollingIfNeeded() {
setUpPollingIfNeeded();
public void registerForFCMIfNeeded(Boolean force) { if (poller != null) {
Context context = this; poller.startIfNeeded();
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> { }
if (!task.isSuccessful()) { if (closedGroupPoller != null) {
Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException()); closedGroupPoller.startIfNeeded();
return; }
} }
String token = task.getResult().getToken();
String userPublicKey = TextSecurePreferences.getLocalNumber(context); public void stopPolling() {
if (userPublicKey == null) return; if (poller != null) {
if (TextSecurePreferences.isUsingFCM(this)) { poller.stopIfNeeded();
LokiPushNotificationManager.register(token, userPublicKey, context, force); }
} else { if (closedGroupPoller != null) {
LokiPushNotificationManager.unregister(token, context); closedGroupPoller.stopIfNeeded();
} }
}); if (publicChatManager != null) {
} publicChatManager.stopPollers();
}
private void setUpPollingIfNeeded() { }
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return; private void resubmitProfilePictureIfNeeded() {
if (poller != null) { String userPublicKey = TextSecurePreferences.getLocalNumber(this);
SnodeAPI.shared.setUserPublicKey(userPublicKey); if (userPublicKey == null) return;
poller.setUserPublicKey(userPublicKey); long now = new Date().getTime();
return; long lastProfilePictureUpload = TextSecurePreferences.getLastProfilePictureUpload(this);
} if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return;
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); AsyncTask.execute(() -> {
Context context = this; String encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this);
SwarmAPI.Companion.configureIfNeeded(apiDB); byte[] profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster); try {
poller = new Poller(userPublicKey, apiDB, envelopes -> { File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey));
for (SignalServiceProtos.Envelope envelope : envelopes) { StreamDetails stream = new StreamDetails(new FileInputStream(profilePicture), "image/jpeg", profilePicture.length());
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(envelope), false); FileServerAPI.shared.uploadProfilePicture(FileServerAPI.shared.getServer(), profileKey, stream, () -> {
} TextSecurePreferences.setLastProfilePictureUpload(this, new Date().getTime());
return Unit.INSTANCE; TextSecurePreferences.setProfileAvatarId(this, new SecureRandom().nextInt());
}); ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey);
ClosedGroupPoller.Companion.configureIfNeeded(this); return Unit.INSTANCE;
closedGroupPoller = ClosedGroupPoller.Companion.getShared(); });
} } catch (Exception exception) {
// Do nothing
public void startPollingIfNeeded() { }
setUpPollingIfNeeded(); });
if (poller != null) { poller.startIfNeeded(); } }
if (closedGroupPoller != null) { closedGroupPoller.startIfNeeded(); }
} public void updateOpenGroupProfilePicturesIfNeeded() {
AsyncTask.execute(() -> {
public void stopPolling() { PublicChatAPI publicChatAPI = null;
if (poller != null) { poller.stopIfNeeded(); } try {
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); } publicChatAPI = getPublicChatAPI();
if (publicChatManager != null) { publicChatManager.stopPollers(); } } catch (Exception e) {
} // Do nothing
}
private void resubmitProfilePictureIfNeeded() { if (publicChatAPI == null) {
String userPublicKey = TextSecurePreferences.getLocalNumber(this); return;
if (userPublicKey == null) return; }
long now = new Date().getTime(); byte[] profileKey = ProfileKeyUtil.getProfileKey(this);
long lastProfilePictureUpload = TextSecurePreferences.getLastProfilePictureUpload(this); String url = TextSecurePreferences.getProfilePictureURL(this);
if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return; Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers();
AsyncTask.execute(() -> { for (String server : servers) {
String encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this); if (profileKey != null) {
byte[] profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey); publicChatAPI.setProfilePicture(server, profileKey, url);
try { }
File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey)); }
StreamDetails stream = new StreamDetails(new FileInputStream(profilePicture), "image/jpeg", profilePicture.length());
FileServerAPI.shared.uploadProfilePicture(FileServerAPI.shared.getServer(), profileKey, stream, () -> {
TextSecurePreferences.setLastProfilePictureUpload(this, new Date().getTime());
TextSecurePreferences.setProfileAvatarId(this, new SecureRandom().nextInt());
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey);
return Unit.INSTANCE;
}); });
} catch (Exception exception) { }
// Do nothing
} public void clearAllData(boolean isMigratingToV2KeyPair) {
}); String token = TextSecurePreferences.getFCMToken(this);
} if (token != null && !token.isEmpty()) {
LokiPushNotificationManager.unregister(token, this);
public void updateOpenGroupProfilePicturesIfNeeded() { }
AsyncTask.execute(() -> { if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) {
PublicChatAPI publicChatAPI = null; firebaseInstanceIdJob.cancel(null);
try { }
publicChatAPI = getPublicChatAPI(); String displayName = TextSecurePreferences.getProfileName(this);
} catch (Exception e) { boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this);
// Do nothing TextSecurePreferences.clearAll(this);
} if (isMigratingToV2KeyPair) {
if (publicChatAPI == null) { return; } TextSecurePreferences.setIsMigratingKeyPair(this, true);
byte[] profileKey = ProfileKeyUtil.getProfileKey(this); TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
String url = TextSecurePreferences.getProfilePictureURL(this); TextSecurePreferences.setProfileName(this, displayName);
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers();
for (String server : servers) {
if (profileKey != null) {
publicChatAPI.setProfilePicture(server, profileKey, url);
} }
} getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
}); if (!deleteDatabase("signal.db")) {
} Log.d("Loki", "Failed to delete database.");
}
public void clearAllData(boolean isMigratingToV2KeyPair) { Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
String token = TextSecurePreferences.getFCMToken(this); }
if (token != null && !token.isEmpty()) {
LokiPushNotificationManager.unregister(token, this); public void restartApplication() {
} Intent intent = new Intent(this, HomeActivity.class);
String displayName = TextSecurePreferences.getProfileName(this); startActivity(Intent.makeRestartActivityTask(intent.getComponent()));
boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this); Runtime.getRuntime().exit(0);
TextSecurePreferences.clearAll(this); }
if (isMigratingToV2KeyPair) {
TextSecurePreferences.setIsMigratingKeyPair(this, true); // endregion
TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
TextSecurePreferences.setProfileName(this, displayName);
}
getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
if (!deleteDatabase("signal.db")) {
Log.d("Loki", "Failed to delete database.");
}
Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200));
}
public void restartApplication() {
Intent intent = new Intent(this, HomeActivity.class);
startActivity(Intent.makeRestartActivityTask(intent.getComponent()));
Runtime.getRuntime().exit(0);
}
// endregion
} }

@ -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)
}
}
Loading…
Cancel
Save