mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-03-01 22:22:15 +00:00
There are situations where we may be hitting our SQLITE_BUSY timeout when we go to trim. One possibility is that we may have a large ongoing write when we go to trim. So, this change just makes sure we're caught up before we go to trim, which is the simplest thing we can do to address this. It's not a foolproof solution though, so if we still see it crop up, we'll just have to re-route all log operations through the single thread we have setup in the PersistentLogger or something.
441 lines
19 KiB
Java
441 lines
19 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.greenrobot.eventbus.EventBus;
|
|
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.tracing.Tracer;
|
|
import org.signal.glide.SignalGlideCodecs;
|
|
import org.signal.ringrtc.CallManager;
|
|
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
import org.thoughtcrime.securesms.database.LogDatabase;
|
|
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader;
|
|
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.EmojiSearchIndexDownloadJob;
|
|
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.PersistentLogger;
|
|
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.SignalLocalMetrics;
|
|
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;
|
|
|
|
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
|
|
|
/**
|
|
* 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();
|
|
SignalLocalMetrics.ColdStart.start();
|
|
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
if (FeatureFlags.internalUser()) {
|
|
Tracer.getInstance().setMaxBufferSize(35_000);
|
|
}
|
|
|
|
super.onCreate();
|
|
|
|
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
|
.addBlocking("sqlcipher-init", () -> SqlCipherLibraryLoader.load())
|
|
.addBlocking("logging", () -> {
|
|
initializeLogging();
|
|
Log.i(TAG, "onCreate()");
|
|
})
|
|
.addBlocking("crash-handling", this::initializeCrashHandling)
|
|
.addBlocking("rx-init", () -> {
|
|
RxJavaPlugins.setInitIoSchedulerHandler(schedulerSupplier -> Schedulers.from(SignalExecutors.BOUNDED_IO, true, false));
|
|
RxJavaPlugins.setInitComputationSchedulerHandler(schedulerSupplier -> Schedulers.from(SignalExecutors.BOUNDED, true, false));
|
|
})
|
|
.addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus())
|
|
.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::cleanAvatarStorage)
|
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
|
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
|
.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(() -> ApplicationDependencies.getGiphyMp4Cache().onAppStart(this))
|
|
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
|
.addPostRender(this::initializeExpiringMessageManager)
|
|
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
|
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
|
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
|
|
.addPostRender(() -> DatabaseFactory.getMessageLogDatabase(this).trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
|
|
.execute();
|
|
|
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
|
SignalLocalMetrics.ColdStart.onApplicationCreateFinished();
|
|
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);
|
|
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
|
|
|
|
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
|
|
|
SignalExecutors.UNBOUNDED.execute(() -> {
|
|
Log.blockUntilAllWritesFinished();
|
|
LogDatabase.getInstance(this).trimToSize();
|
|
});
|
|
}
|
|
|
|
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 initializePendingRetryReceiptManager() {
|
|
ApplicationDependencies.getPendingRetryReceiptManager().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 cleanAvatarStorage() {
|
|
AvatarPickerStorage.cleanOrphans(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 {
|
|
}
|
|
}
|