405 lines
17 KiB
Java
405 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2013 Open Whisper Systems
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.thoughtcrime.securesms;
|
|
|
|
import android.content.Context;
|
|
import android.os.Build;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.WorkerThread;
|
|
import androidx.appcompat.app.AppCompatDelegate;
|
|
import androidx.multidex.MultiDexApplication;
|
|
|
|
import com.google.android.gms.security.ProviderInstaller;
|
|
|
|
import org.conscrypt.Conscrypt;
|
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
|
import org.signal.core.util.concurrent.SignalExecutors;
|
|
import org.signal.core.util.logging.AndroidLogger;
|
|
import org.signal.core.util.logging.Log;
|
|
import org.signal.core.util.logging.PersistentLogger;
|
|
import org.signal.core.util.tracing.Tracer;
|
|
import org.signal.glide.SignalGlideCodecs;
|
|
import org.signal.ringrtc.CallManager;
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
|
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
|
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
|
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
|
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
|
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
|
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
|
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
|
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
|
|
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
|
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
|
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
|
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
|
import org.thoughtcrime.securesms.util.AppStartup;
|
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
import org.thoughtcrime.securesms.util.VersionTracker;
|
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
|
import org.webrtc.voiceengine.WebRtcAudioManager;
|
|
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
|
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
|
|
|
import java.security.Security;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* Will be called once when the TextSecure process is created.
|
|
*
|
|
* 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.
|
|
*
|
|
* @author Moxie Marlinspike
|
|
*/
|
|
public class ApplicationContext extends MultiDexApplication implements AppForegroundObserver.Listener {
|
|
|
|
private static final String TAG = Log.tag(ApplicationContext.class);
|
|
|
|
private PersistentLogger persistentLogger;
|
|
|
|
public static ApplicationContext getInstance(Context context) {
|
|
return (ApplicationContext)context.getApplicationContext();
|
|
}
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
Tracer.getInstance().start("Application#onCreate()");
|
|
AppStartup.getInstance().onApplicationCreate();
|
|
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
if (FeatureFlags.internalUser()) {
|
|
Tracer.getInstance().setMaxBufferSize(35_000);
|
|
}
|
|
|
|
super.onCreate();
|
|
|
|
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
|
.addBlocking("logging", () -> {
|
|
initializeLogging();
|
|
Log.i(TAG, "onCreate()");
|
|
})
|
|
.addBlocking("crash-handling", this::initializeCrashHandling)
|
|
.addBlocking("eat-db", () -> DatabaseFactory.getInstance(this))
|
|
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
|
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
|
|
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
|
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
|
.addBlocking("ring-rtc", this::initializeRingRtc)
|
|
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
|
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
|
|
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
|
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
|
.addBlocking("vector-compat", () -> {
|
|
if (Build.VERSION.SDK_INT < 21) {
|
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
|
}
|
|
})
|
|
.addBlocking("proxy-init", () -> {
|
|
if (SignalStore.proxy().isProxyEnabled()) {
|
|
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
|
Conscrypt.setUseEngineSocketByDefault(true);
|
|
}
|
|
})
|
|
.addBlocking("blob-provider", this::initializeBlobProvider)
|
|
.addBlocking("feature-flags", FeatureFlags::init)
|
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
|
.addNonBlocking(this::initializeGcmCheck)
|
|
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
|
.addNonBlocking(this::initializePeriodicTasks)
|
|
.addNonBlocking(this::initializeCircumvention)
|
|
.addNonBlocking(this::initializePendingMessages)
|
|
.addNonBlocking(this::initializeCleanup)
|
|
.addNonBlocking(this::initializeGlideCodecs)
|
|
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
|
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
|
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
|
.addNonBlocking(EmojiSource::refresh)
|
|
.addNonBlocking(DownloadLatestEmojiDataJob::scheduleIfNecessary)
|
|
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
|
.addPostRender(this::initializeExpiringMessageManager)
|
|
.execute();
|
|
|
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
|
Tracer.getInstance().end("Application#onCreate()");
|
|
}
|
|
|
|
@Override
|
|
public void onForeground() {
|
|
long startTime = System.currentTimeMillis();
|
|
Log.i(TAG, "App is now visible.");
|
|
|
|
ApplicationDependencies.getFrameRateTracker().begin();
|
|
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
|
|
|
SignalExecutors.BOUNDED.execute(() -> {
|
|
FeatureFlags.refreshIfNecessary();
|
|
ApplicationDependencies.getRecipientCache().warmUp();
|
|
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
|
|
GroupV1MigrationJob.enqueueRoutineMigrationsIfNecessary(this);
|
|
executePendingContactSync();
|
|
KeyCachingService.onAppForegrounded(this);
|
|
ApplicationDependencies.getShakeToReport().enable();
|
|
checkBuildExpiration();
|
|
});
|
|
|
|
Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms");
|
|
}
|
|
|
|
@Override
|
|
public void onBackground() {
|
|
Log.i(TAG, "App is no longer visible.");
|
|
KeyCachingService.onAppBackgrounded(this);
|
|
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
|
ApplicationDependencies.getFrameRateTracker().end();
|
|
ApplicationDependencies.getShakeToReport().disable();
|
|
}
|
|
|
|
public PersistentLogger getPersistentLogger() {
|
|
return persistentLogger;
|
|
}
|
|
|
|
public void checkBuildExpiration() {
|
|
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
|
|
Log.w(TAG, "Build expired!");
|
|
SignalStore.misc().markClientDeprecated();
|
|
}
|
|
}
|
|
|
|
private void initializeSecurityProvider() {
|
|
try {
|
|
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
|
} catch (ClassNotFoundException e) {
|
|
Log.e(TAG, "Failed to find AesGcmCipher class");
|
|
throw new ProviderInitializationException();
|
|
}
|
|
|
|
int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
|
|
Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
|
|
|
|
if (aesPosition < 0) {
|
|
Log.e(TAG, "Failed to install AesGcmProvider()");
|
|
throw new ProviderInitializationException();
|
|
}
|
|
|
|
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
|
|
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
|
|
|
|
if (conscryptPosition < 0) {
|
|
Log.w(TAG, "Did not install Conscrypt provider. May already be present.");
|
|
}
|
|
}
|
|
|
|
private void initializeLogging() {
|
|
persistentLogger = new PersistentLogger(this, LogSecretProvider.getOrCreateAttachmentSecret(this), BuildConfig.VERSION_NAME);
|
|
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
|
|
|
|
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
|
}
|
|
|
|
private void initializeCrashHandling() {
|
|
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
Thread.setDefaultUncaughtExceptionHandler(new SignalUncaughtExceptionHandler(originalHandler));
|
|
}
|
|
|
|
private void initializeApplicationMigrations() {
|
|
ApplicationMigrations.onApplicationCreate(this, ApplicationDependencies.getJobManager());
|
|
}
|
|
|
|
public void initializeMessageRetrieval() {
|
|
ApplicationDependencies.getIncomingMessageObserver();
|
|
}
|
|
|
|
private void initializeAppDependencies() {
|
|
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
|
|
}
|
|
|
|
private void initializeFirstEverAppLaunch() {
|
|
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
|
if (!SQLCipherOpenHelper.databaseFileExists(this) || VersionTracker.getDaysSinceFirstInstalled(this) < 365) {
|
|
Log.i(TAG, "First ever app launch!");
|
|
AppInitialization.onFirstEverAppLaunch(this);
|
|
}
|
|
|
|
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
|
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
|
|
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
|
|
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
|
|
AppInitialization.onRepairFirstEverAppLaunch(this);
|
|
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 912) {
|
|
Log.i(TAG, "Detected a not-recent install that doesn't have passphrases disabled -- disabling now.");
|
|
TextSecurePreferences.setPasswordDisabled(this, true);
|
|
}
|
|
}
|
|
|
|
private void initializeGcmCheck() {
|
|
if (TextSecurePreferences.isPushRegistered(this)) {
|
|
long nextSetTime = TextSecurePreferences.getFcmTokenLastSetTime(this) + TimeUnit.HOURS.toMillis(6);
|
|
|
|
if (TextSecurePreferences.getFcmToken(this) == null || nextSetTime <= System.currentTimeMillis()) {
|
|
ApplicationDependencies.getJobManager().add(new FcmRefreshJob());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void initializeSignedPreKeyCheck() {
|
|
if (!TextSecurePreferences.isSignedPreKeyRegistered(this)) {
|
|
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob(this));
|
|
}
|
|
}
|
|
|
|
private void initializeExpiringMessageManager() {
|
|
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
|
}
|
|
|
|
private void initializeRevealableMessageManager() {
|
|
ApplicationDependencies.getViewOnceMessageManager().scheduleIfNecessary();
|
|
}
|
|
|
|
private void initializePeriodicTasks() {
|
|
RotateSignedPreKeyListener.schedule(this);
|
|
DirectoryRefreshListener.schedule(this);
|
|
LocalBackupListener.schedule(this);
|
|
RotateSenderCertificateListener.schedule(this);
|
|
MessageProcessReceiver.startOrUpdateAlarm(this);
|
|
|
|
if (BuildConfig.PLAY_STORE_DISABLED) {
|
|
UpdateApkRefreshListener.schedule(this);
|
|
}
|
|
}
|
|
|
|
private void initializeRingRtc() {
|
|
try {
|
|
if (RtcDeviceLists.hardwareAECBlocked()) {
|
|
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
|
|
}
|
|
|
|
if (!RtcDeviceLists.openSLESAllowed()) {
|
|
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
|
}
|
|
|
|
CallManager.initialize(this, new RingRtcLogger());
|
|
} catch (UnsatisfiedLinkError e) {
|
|
throw new AssertionError("Unable to load ringrtc library", e);
|
|
}
|
|
}
|
|
|
|
@WorkerThread
|
|
private void initializeCircumvention() {
|
|
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
|
try {
|
|
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
|
} catch (Throwable t) {
|
|
Log.w(TAG, t);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void executePendingContactSync() {
|
|
if (TextSecurePreferences.needsFullContactSync(this)) {
|
|
ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob(true));
|
|
}
|
|
}
|
|
|
|
private void initializePendingMessages() {
|
|
if (TextSecurePreferences.getNeedsMessagePull(this)) {
|
|
Log.i(TAG, "Scheduling a message fetch.");
|
|
if (Build.VERSION.SDK_INT >= 26) {
|
|
FcmJobService.schedule(this);
|
|
} else {
|
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
|
}
|
|
TextSecurePreferences.setNeedsMessagePull(this, false);
|
|
}
|
|
}
|
|
|
|
@WorkerThread
|
|
private void initializeBlobProvider() {
|
|
BlobProvider.getInstance().initialize(this);
|
|
}
|
|
|
|
@WorkerThread
|
|
private void initializeCleanup() {
|
|
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
|
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
|
}
|
|
|
|
private void initializeGlideCodecs() {
|
|
SignalGlideCodecs.setLogProvider(new org.signal.glide.Log.Provider() {
|
|
@Override
|
|
public void v(@NonNull String tag, @NonNull String message) {
|
|
Log.v(tag, message);
|
|
}
|
|
|
|
@Override
|
|
public void d(@NonNull String tag, @NonNull String message) {
|
|
Log.d(tag, message);
|
|
}
|
|
|
|
@Override
|
|
public void i(@NonNull String tag, @NonNull String message) {
|
|
Log.i(tag, message);
|
|
}
|
|
|
|
@Override
|
|
public void w(@NonNull String tag, @NonNull String message) {
|
|
Log.w(tag, message);
|
|
}
|
|
|
|
@Override
|
|
public void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
|
Log.e(tag, message, throwable);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
protected void attachBaseContext(Context base) {
|
|
DynamicLanguageContextWrapper.updateContext(base);
|
|
super.attachBaseContext(base);
|
|
}
|
|
|
|
private static class ProviderInitializationException extends RuntimeException {
|
|
}
|
|
}
|