// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.init;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Process;
import android.text.format.DateUtils;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import androidx.annotation.CallSuper;
import androidx.annotation.WorkerThread;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BuildInfo;
import org.chromium.base.ContextUtils;
import org.chromium.base.FileProviderUtils;
import org.chromium.base.Log;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.memory.MemoryPressureUma;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.ChainedTasks;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.version_info.VersionInfo;
import org.chromium.build.BuildConfig;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ChromeActivitySessionTracker;
import org.chromium.chrome.browser.ChromeApplicationImpl;
import org.chromium.chrome.browser.ChromeBackupAgentImpl;
import org.chromium.chrome.browser.ChromeStrictMode;
import org.chromium.chrome.browser.DefaultBrowserInfo;
import org.chromium.chrome.browser.DeferredStartupHandler;
import org.chromium.chrome.browser.DevToolsServer;
import org.chromium.chrome.browser.FileProviderHelper;
import org.chromium.chrome.browser.app.bluetooth.BluetoothNotificationService;
import org.chromium.chrome.browser.app.flags.ChromeCachedFlags;
import org.chromium.chrome.browser.app.usb.UsbNotificationService;
import org.chromium.chrome.browser.bluetooth.BluetoothNotificationManager;
import org.chromium.chrome.browser.bookmarkswidget.BookmarkWidgetProvider;
import org.chromium.chrome.browser.contacts_picker.ChromePickerAdapter;
import org.chromium.chrome.browser.content_capture.ContentCaptureHistoryDeletionObserver;
import org.chromium.chrome.browser.crash.CrashUploadCountStore;
import org.chromium.chrome.browser.crash.LogcatExtractionRunnable;
import org.chromium.chrome.browser.crash.MinidumpUploadServiceImpl;
import org.chromium.chrome.browser.download.DownloadManagerService;
import org.chromium.chrome.browser.download.OfflineContentAvailabilityStatusProvider;
import org.chromium.chrome.browser.enterprise.util.EnterpriseInfo;
import org.chromium.chrome.browser.firstrun.TosDialogBehaviorSharedPrefInvalidator;
import org.chromium.chrome.browser.history.HistoryDeletionBridge;
import org.chromium.chrome.browser.homepage.HomepageManager;
import org.chromium.chrome.browser.incognito.IncognitoTabLauncher;
import org.chromium.chrome.browser.language.GlobalAppLocaleController;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.media.MediaCaptureNotificationServiceImpl;
import org.chromium.chrome.browser.media.MediaViewerUtils;
import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.PackageMetrics;
import org.chromium.chrome.browser.metrics.UmaUtils;
import org.chromium.chrome.browser.notifications.channels.ChannelsUpdater;
import org.chromium.chrome.browser.ntp.FeedPositionUtils;
import org.chromium.chrome.browser.offlinepages.measurements.OfflineMeasurementsBackgroundTask;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactory;
import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
import org.chromium.chrome.browser.photo_picker.DecoderService;
import org.chromium.chrome.browser.preferences.AllPreferenceKeyRegistries;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileKeyedMap;
import org.chromium.chrome.browser.profiles.ProfileKeyedMap.ProfileSelection;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.profiles.ProfileManagerUtils;
import org.chromium.chrome.browser.quickactionsearchwidget.QuickActionSearchWidgetProvider;
import org.chromium.chrome.browser.rlz.RevenueStats;
import org.chromium.chrome.browser.searchwidget.SearchWidgetProvider;
import org.chromium.chrome.browser.signin.SigninCheckerProvider;
import org.chromium.chrome.browser.tab.state.PersistedTabData;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
import org.chromium.chrome.browser.ui.cars.DrivingRestrictionsManager;
import org.chromium.chrome.browser.ui.hats.SurveyClientFactory;
import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityPreferencesManager;
import org.chromium.chrome.browser.usb.UsbNotificationManager;
import org.chromium.chrome.browser.util.AfterStartupTaskUtils;
import org.chromium.chrome.browser.webapps.ChromeWebApkHost;
import org.chromium.chrome.browser.webapps.WebApkUninstallTracker;
import org.chromium.chrome.browser.webapps.WebappRegistry;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.browser_ui.contacts_picker.ContactsPickerDialog;
import org.chromium.components.browser_ui.photo_picker.DecoderServiceHost;
import org.chromium.components.browser_ui.photo_picker.PhotoPickerDelegateBase;
import org.chromium.components.browser_ui.photo_picker.PhotoPickerDialog;
import org.chromium.components.browser_ui.share.ClipboardImageFileProvider;
import org.chromium.components.browser_ui.share.ShareImageFileUtils;
import org.chromium.components.content_capture.PlatformContentCaptureController;
import org.chromium.components.crash.anr.AnrCollector;
import org.chromium.components.crash.browser.ChildProcessCrashObserver;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.minidump_uploader.CrashFileManager;
import org.chromium.components.module_installer.util.ModuleUtil;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.components.policy.CombinedPolicyProvider;
import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
import org.chromium.components.signin.AccountManagerFacadeImpl;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.webapps.AppBannerManager;
import org.chromium.content_public.browser.ChildProcessLauncherHelper;
import org.chromium.content_public.browser.ContactsPicker;
import org.chromium.content_public.browser.ContactsPickerListener;
import org.chromium.content_public.browser.DeviceUtils;
import org.chromium.content_public.browser.SpeechRecognition;
import org.chromium.content_public.browser.WebContents;
import org.chromium.net.NetworkChangeNotifier;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.base.Clipboard;
import org.chromium.ui.base.PhotoPicker;
import org.chromium.ui.base.PhotoPickerListener;
import org.chromium.ui.base.SelectFileDialog;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.GURL;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* Handles the initialization dependences of the browser process. This is meant to handle the
* initialization that is not tied to any particular Activity, and the logic that should only be
* triggered a single time for the lifetime of the browser process.
*/
public class ProcessInitializationHandler {
private static final String TAG = "ProcessInitHandler";
private static final String DEV_TOOLS_SERVER_SOCKET_PREFIX = "chrome";
/** Prevents race conditions when deleting snapshot database. */
private static final Object SNAPSHOT_DATABASE_LOCK = new Object();
private static final String SNAPSHOT_DATABASE_NAME = "snapshots.db";
private static ProcessInitializationHandler sInstance;
private boolean mInitializedPreNative;
private boolean mInitializedPreNativeLibraryLoad;
private boolean mInitializedPostNative;
private boolean mInitializedPostNativeFollowingActivityInit;
private boolean mInitializedDeferredStartupTasks;
private boolean mNetworkChangeNotifierInitializationComplete;
private final Locale mInitialLocale = Locale.getDefault();
private DevToolsServer mDevToolsServer;
private final ProfileKeyedMap<Boolean> mStartupProfileTasksCompleted =
new ProfileKeyedMap<>(
ProfileSelection.REDIRECTED_TO_ORIGINAL,
ProfileKeyedMap.NO_REQUIRED_CLEANUP_ACTION);
/**
* @return The ProcessInitializationHandler for use during the lifetime of the browser process.
*/
public static ProcessInitializationHandler getInstance() {
ThreadUtils.checkUiThread();
if (sInstance == null) {
sInstance = AppHooks.get().createProcessInitializationHandler();
}
return sInstance;
}
/**
* Initializes the dependencies that are required and used before native library loading.
*
* <p>If a dependency should be initialized early but only is utilizes when the native library
* has been loaded, then add that dependency in {@link
* #handlePreNativeLibraryLoadInitialization()}.
*
* <p>Adding anything expensive to this must be avoided as it would delay the Chrome startup
* path.
*
* <p>All entry points that do not rely on {@link ChromeBrowserInitializer} must call this on
* startup.
*/
public final void initializePreNative() {
try (TraceEvent e =
TraceEvent.scoped("ProcessInitializationHandler.initializePreNative()")) {
ThreadUtils.checkUiThread();
if (mInitializedPreNative) return;
handlePreNativeInitialization();
mInitializedPreNative = true;
}
}
/** Performs the shared class initialization. */
@CallSuper
protected void handlePreNativeInitialization() {
// Initialize the AccountManagerFacade with the correct AccountManagerDelegate. Must be done
// only once and before AccountManagerFacadeProvider.getInstance() is invoked.
AccountManagerFacadeProvider.setInstance(
new AccountManagerFacadeImpl(AppHooks.get().createAccountManagerDelegate()));
setProcessStateSummaryForAnrs(false);
}
/**
* Initializes the dependencies that must occur before native library has been loaded.
*
* <p>Adding anything expensive to this must be avoided as it would delay the Chrome startup
* path.
*
* <p>All entry points that do not rely on {@link ChromeBrowserInitializer} must call this on
* startup.
*/
public final void initializePreNativeLibraryLoad() {
try (TraceEvent e =
TraceEvent.scoped(
"ProcessInitializationHandler.initializePreNativeLibraryLoad()")) {
ThreadUtils.checkUiThread();
if (mInitializedPreNativeLibraryLoad) return;
handlePreNativeLibraryLoadInitialization();
mInitializedPreNativeLibraryLoad = true;
}
}
/**
* Performs the shared class initialization of dependencies that need to be initialized
* immediately before native library loading and initialization.
*/
@CallSuper
protected void handlePreNativeLibraryLoadInitialization() {
new Thread(SafeBrowsingApiBridge::ensureSafetyNetApiInitialized).start();
new Thread(SafeBrowsingApiBridge::initSafeBrowsingApi).start();
// Ensure critical files are available, so they aren't blocked on the file-system
// behind long-running accesses in next phase.
// Don't do any large file access here!
ChromeStrictMode.configureStrictMode();
ChromeWebApkHost.init();
// In ENABLE_ASSERTS builds, initialize SharedPreferences key registry checking.
if (BuildConfig.ENABLE_ASSERTS) {
AllPreferenceKeyRegistries.initializeKnownRegistries();
}
// Time this call takes in background from test devices:
// - Pixel 2: ~10 ms
// - Nokia 1 (Android Go): 20-200 ms
warmUpSharedPrefs();
DeviceUtils.addDeviceSpecificUserAgentSwitch();
ApplicationStatus.registerStateListenerForAllActivities(
(activity, newState) -> {
if (newState == ActivityState.CREATED || newState == ActivityState.DESTROYED) {
// When the app locale is overridden a change in system locale will not
// effect Chrome's UI language. There is race condition where the initial
// locale may not equal the overridden default locale
// (https://crbug.com/1224756).
if (GlobalAppLocaleController.getInstance().isOverridden()) return;
// Android destroys Activities at some point after a locale change, but
// doesn't kill the process. This can lead to a bug where Chrome is halfway
// RTL, where stale natively-loaded resources are not reloaded
// (http://crbug.com/552618).
if (!mInitialLocale.equals(Locale.getDefault())) {
Log.e(TAG, "Killing process because of locale change.");
Process.killProcess(Process.myPid());
}
}
});
}
/**
* Pre-load shared prefs to avoid being blocked on the disk access async task in the future.
* Running in an AsyncTask as pre-loading itself may cause I/O.
*/
private void warmUpSharedPrefs() {
PostTask.postTask(
TaskTraits.BEST_EFFORT_MAY_BLOCK,
() -> {
DownloadManagerService.warmUpSharedPrefs();
});
}
/**
* Enqueues tasks that should be run before any Activity (or similar Android entry point) begins
* their respective post native initialization.
*
* <p>There is no guarantee that these tasks are completed, so each task should individually
* track their own corresponding completeness status and ensure subsequent calls only run the
* required work.
*
* @param tasks The ordered list of startup tasks to be run.
* @param minimalBrowserMode Whether this is being started in minimal mode.
*/
public final void enqueuePostNativeTasksToRunBeforeActivityNativeInit(
ChainedTasks tasks, boolean minimalBrowserMode) {
ThreadUtils.checkUiThread();
// If full browser process is not going to be launched, it is up to individual service to
// launch its required components.
if (!minimalBrowserMode && !mInitializedPostNative) {
tasks.add(
TaskTraits.UI_DEFAULT,
() -> {
if (mInitializedPostNative) return;
handlePostNativeInitialization();
mInitializedPostNative = true;
});
}
if (!mNetworkChangeNotifierInitializationComplete) {
tasks.add(TaskTraits.UI_DEFAULT, this::initNetworkChangeNotifier);
}
}
/**
* Enqueues tasks that should be run after any Activity (or similar Android entry point)
* completes their respective post native initialization.
*
* <p>There is no guarantee that these tasks are completed, so each task should individually
* track their own corresponding completeness status and ensure subsequent calls only run the
* required work.
*
* @param tasks The ordered list of startup tasks to be run.
*/
public final void enqueuePostNativeTasksToRunAfterActivityNativeInit(ChainedTasks tasks) {
if (!mInitializedPostNativeFollowingActivityInit) {
tasks.add(
TaskTraits.UI_DEFAULT,
() -> {
if (mInitializedPostNativeFollowingActivityInit) return;
handlePostNativeInitializationFollowingActivityInit();
mInitializedPostNativeFollowingActivityInit = true;
});
}
}
/** Performs the post native initialization. */
@CallSuper
protected void handlePostNativeInitialization() {
ChromeActivitySessionTracker.getInstance().initializeWithNative();
ProfileManagerUtils.removeSessionCookiesForAllProfiles();
AppBannerManager.setAppDetailsDelegate(AppHooks.get().createAppDetailsDelegate());
ChromeLifetimeController.initialize();
Clipboard.getInstance().setImageFileProvider(new ClipboardImageFileProvider());
DecoderServiceHost.setIntentSupplier(
() -> {
return new Intent(ContextUtils.getApplicationContext(), DecoderService.class);
});
SelectFileDialog.setPhotoPickerDelegate(
new PhotoPickerDelegateBase() {
@Override
public PhotoPicker showPhotoPicker(
WindowAndroid windowAndroid,
PhotoPickerListener listener,
boolean allowMultiple,
List<String> mimeTypes) {
PhotoPickerDialog dialog =
new PhotoPickerDialog(
windowAndroid,
windowAndroid.getContext().get().getContentResolver(),
listener,
allowMultiple,
mimeTypes);
dialog.getWindow().getAttributes().windowAnimations =
R.style.PickerDialogAnimation;
dialog.show();
return dialog;
}
});
ContactsPicker.setContactsPickerDelegate(
(WebContents webContents,
ContactsPickerListener listener,
boolean allowMultiple,
boolean includeNames,
boolean includeEmails,
boolean includeTel,
boolean includeAddresses,
boolean includeIcons,
String formattedOrigin) -> {
WindowAndroid windowAndroid = webContents.getTopLevelNativeWindow();
ContactsPickerDialog dialog =
new ContactsPickerDialog(
windowAndroid,
new ChromePickerAdapter(
windowAndroid.getContext().get(),
Profile.fromWebContents(webContents)),
listener,
allowMultiple,
includeNames,
includeEmails,
includeTel,
includeAddresses,
includeIcons,
formattedOrigin);
dialog.getWindow().getAttributes().windowAnimations =
R.style.PickerDialogAnimation;
dialog.show();
return dialog;
});
SearchActivityPreferencesManager.onNativeLibraryReady();
SearchWidgetProvider.initialize();
QuickActionSearchWidgetProvider.initialize();
PrivacyPreferencesManagerImpl.getInstance().onNativeInitialized();
setProcessStateSummaryForAnrs(true);
List<Profile> profiles = ProfileManager.getLoadedProfiles();
assert !profiles.isEmpty()
: "At least one Profile should be loaded before post native init.";
for (Profile profile : profiles) {
handleProfileDependentPostNativeInitialization(profile);
}
ProfileManager.addObserver(
new ProfileManager.Observer() {
@Override
public void onProfileAdded(Profile profile) {
if (profile.isOffTheRecord()) return;
handleProfileDependentPostNativeInitialization(profile);
}
@Override
public void onProfileDestroyed(Profile profile) {}
});
AccessibilityState.registerObservers();
if (BuildInfo.getInstance().isAutomotive) {
DrivingRestrictionsManager.initialize();
}
// Initialize UMA settings for survey component.
// TODO(crbug.com/40281638): Observe PrivacyPreferencesManagerImpl from SurveyClientFactory.
ObservableSupplierImpl<Boolean> crashUploadPermissionSupplier =
new ObservableSupplierImpl<>();
crashUploadPermissionSupplier.set(
PrivacyPreferencesManagerImpl.getInstance().isUsageAndCrashReportingPermitted());
PrivacyPreferencesManagerImpl.getInstance().addObserver(crashUploadPermissionSupplier::set);
SurveyClientFactory.initialize(crashUploadPermissionSupplier);
AppHooks.get().registerPolicyProviders(CombinedPolicyProvider.get());
SpeechRecognition.initialize();
}
/**
* Handles additional post native initialization that should be run after the triggering
* Activity (or other Android entry point) has completed their native init.
*/
@CallSuper
protected void handlePostNativeInitializationFollowingActivityInit() {
FileProviderUtils.setFileProviderUtil(new FileProviderHelper());
// When a child process crashes, search for the most recent minidump for the child's process
// ID and attach a logcat to it. Then upload it to the crash server. Note that the logcat
// extraction might fail. This is ok; in that case, the minidump will be found and uploaded
// upon the next browser launch.
ChildProcessCrashObserver.registerCrashCallback(
new ChildProcessCrashObserver.ChildCrashedCallback() {
@Override
public void childCrashed(int pid) {
CrashFileManager crashFileManager =
new CrashFileManager(
ContextUtils.getApplicationContext().getCacheDir());
File minidump = crashFileManager.getMinidumpSansLogcatForPid(pid);
if (minidump != null) {
AsyncTask.THREAD_POOL_EXECUTOR.execute(
new LogcatExtractionRunnable(minidump));
} else {
Log.e(TAG, "Missing dump for child " + pid);
}
}
});
MemoryPressureUma.initializeForBrowser();
UmaUtils.recordBackgroundRestrictions();
// Needed for field trial metrics to be properly collected in minimal browser mode.
ChromeCachedFlags.getInstance().cacheMinimalBrowserFlags();
ModuleUtil.recordStartupTime();
}
public final void initNetworkChangeNotifier() {
if (mNetworkChangeNotifierInitializationComplete) return;
mNetworkChangeNotifierInitializationComplete = true;
ThreadUtils.assertOnUiThread();
TraceEvent.begin("NetworkChangeNotifier.init");
// Enable auto-detection of network connectivity state changes.
NetworkChangeNotifier.init();
NetworkChangeNotifier.setAutoDetectConnectivityState(true);
TraceEvent.end("NetworkChangeNotifier.init");
}
/**
* Handle per-{@link Profile} post native initialization.
*
* <p>This will be called for each non-incognito {@link Profile} that is loaded and initialized.
*/
@CallSuper
protected void handleProfileDependentPostNativeInitialization(Profile profile) {
FeedPositionUtils.cacheSegmentationResult(profile);
HistoryDeletionBridge.getForProfile(profile)
.addObserver(
new ContentCaptureHistoryDeletionObserver(
() -> PlatformContentCaptureController.getInstance()));
}
/**
* We use the Android API to store key information which we can't afford to have wrong on our
* ANR reports. So, we set the version number, and the main .so file's Build ID once native has
* been loaded. Then, when we query Android for any ANRs that have happened, we can also pull
* these key fields.
*
* <p>We are limited to 128 bytes in ProcessStateSummary, so we only store the most important
* things that can change between the ANR happening and an upload (when the rest of the metadata
* is gathered). Some fields we ignore because they won't change (eg. which channel or what the
* .so filename is) and some we ignore because they aren't as critical (eg. experiments). In the
* future, we could make this point to a file where we would write out all our crash keys, and
* thus get full fidelity.
*/
protected void setProcessStateSummaryForAnrs(boolean includeNative) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ActivityManager am =
(ActivityManager)
ContextUtils.getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE);
String summary = VersionInfo.getProductVersion();
if (includeNative) {
summary += "," + AnrCollector.getSharedLibraryBuildId();
}
am.setProcessStateSummary(summary.getBytes(StandardCharsets.UTF_8));
}
}
/**
* Handle application level deferred startup tasks that can be lazily done after all
* the necessary initialization has been completed. Should only be triggered once per browser
* process lifetime. Any calls requiring network access should probably go here.
*
* Keep these tasks short and break up long tasks into multiple smaller tasks, as they run on
* the UI thread and are blocking. Remember to follow RAIL guidelines, as much as possible, and
* that most devices are quite slow, so leave enough buffer.
*/
public final void initializeDeferredStartupTasks() {
ThreadUtils.checkUiThread();
if (mInitializedDeferredStartupTasks) return;
mInitializedDeferredStartupTasks = true;
DeferredStartupHandler deferredStartupHandler = DeferredStartupHandler.getInstance();
List<Runnable> deferredTasks = new ArrayList<>();
addPerApplicationStartupDeferredTasks(deferredTasks);
deferredStartupHandler.addDeferredTasks(deferredTasks);
}
/**
* Handle per-profile level deferred startup tasks that can be lazily done after all the
* necessary initialization has been completed. Should only be triggered once per profile
* lifetime. Any calls requiring network access should probably go here.
*
* @param profile The Profile associated with the startup tasks.
* @see #initializeDeferredStartupTasks() for timing considerations.
*/
public final void initializeProfileDependentDeferredStartupTasks(Profile profile) {
ThreadUtils.checkUiThread();
// Ignore the return value as ProfileKeyedMap is used to track that these actions are
// handled at most once per Profile.
mStartupProfileTasksCompleted.getForProfile(
profile, this::handlePerProfileDeferredStartupTasksInitialization);
}
private boolean handlePerProfileDeferredStartupTasksInitialization(Profile profile) {
DeferredStartupHandler deferredStartupHandler = DeferredStartupHandler.getInstance();
List<Runnable> deferredTasks = new ArrayList<>();
addPerProfileStartupDeferredTasks(profile, deferredTasks);
deferredStartupHandler.addDeferredTasks(deferredTasks);
return true; // Return a non-null value to ensure ProfileKeyedMap tracks this was completed.
}
/**
* Adds all the deferred startup tasks that should be called exactly once for the lifetime of
* the application.
*
* @param tasks The list where new tasks should be added.
*/
@CallSuper
protected void addPerApplicationStartupDeferredTasks(List<Runnable> tasks) {
tasks.add(
() -> {
initAsyncDiskTask();
DefaultBrowserInfo.initBrowserFetcher();
AfterStartupTaskUtils.setStartupComplete();
PartnerBrowserCustomizations.getInstance()
.setOnInitializeAsyncFinished(
() -> {
HomepageManager homepageManager =
HomepageManager.getInstance();
GURL homepageGurl = homepageManager.getHomepageGurl();
LaunchMetrics.recordHomePageLaunchMetrics(
homepageManager.isHomepageEnabled(),
UrlUtilities.isNtpUrl(homepageGurl),
homepageGurl);
});
ShareImageFileUtils.clearSharedImages();
SelectFileDialog.clearCapturedCameraFiles();
if (ChannelsUpdater.getInstance().shouldUpdateChannels()) {
initChannelsAsync();
}
});
tasks.add(
() -> {
// Clear notifications that existed when Chrome was last killed.
MediaCaptureNotificationServiceImpl.clearMediaNotifications();
BluetoothNotificationManager.clearBluetoothNotifications(
BluetoothNotificationService.class);
UsbNotificationManager.clearUsbNotifications(UsbNotificationService.class);
startBindingManagementIfNeeded();
recordKeyboardLocaleUma();
});
tasks.add(() -> LocaleManager.getInstance().recordStartupMetrics());
tasks.add(() -> HomepageManager.getInstance().recordHomepageLocationTypeIfEnabled());
// Record the saved restore state in a histogram
tasks.add(ChromeBackupAgentImpl::recordRestoreHistogram);
tasks.add(RevenueStats::getInstance);
tasks.add(
() -> {
mDevToolsServer = new DevToolsServer(DEV_TOOLS_SERVER_SOCKET_PREFIX);
mDevToolsServer.setRemoteDebuggingEnabled(
true, DevToolsServer.Security.ALLOW_DEBUG_PERMISSION);
});
tasks.add(() -> BackgroundTaskSchedulerFactory.getScheduler().doMaintenance());
tasks.add(MediaViewerUtils::updateMediaLauncherActivityEnabled);
tasks.add(
ChromeApplicationImpl.getComponent().resolveClearDataDialogResultRecorder()
::makeDeferredRecordings);
tasks.add(WebApkUninstallTracker::runDeferredTasks);
tasks.add(OfflineContentAvailabilityStatusProvider::getInstance);
tasks.add(() -> EnterpriseInfo.getInstance().logDeviceEnterpriseInfo());
tasks.add(TosDialogBehaviorSharedPrefInvalidator::refreshSharedPreferenceIfTosSkipped);
tasks.add(OfflineMeasurementsBackgroundTask::clearPersistedDataFromPrefs);
tasks.add(
() -> {
GlobalAppLocaleController.getInstance().maybeSetupLocaleManager();
GlobalAppLocaleController.getInstance().recordOverrideLanguageMetrics();
});
tasks.add(PersistedTabData::onDeferredStartup);
// Asynchronously query system accessibility state so it is ready for clients.
tasks.add(AccessibilityState::initializeOnStartup);
tasks.add(TabPersistentStore::onDeferredStartup);
}
/**
* Adds all the deferred startup tasks that should be called exactly once for the lifetime of a
* profile.
*
* @param profile The profile triggering the startup tasks.
* @param tasks The list where new tasks should be added.
*/
@CallSuper
protected void addPerProfileStartupDeferredTasks(Profile profile, List<Runnable> tasks) {
// TODO(crbug.com/40254448): Determine how IncognitoTabLauncher should support multiple
// concurrent profiles. Currently, the enabled state will change based on the Profile
// initialization order.
tasks.add(() -> IncognitoTabLauncher.updateComponentEnabledState(profile));
tasks.add(() -> SigninCheckerProvider.get(profile).onMainActivityStart());
tasks.add(
() -> {
OptimizationGuideBridge optimizationGuideBridge =
OptimizationGuideBridgeFactory.getForProfile(profile);
if (optimizationGuideBridge != null) {
// OptimizationTypes which we give a guarantee will be registered when we
// pass the onDeferredStartup() signal to OptimizationGuide.
optimizationGuideBridge.registerOptimizationTypes(
Arrays.asList(HintsProto.OptimizationType.PRICE_TRACKING));
optimizationGuideBridge.onDeferredStartup();
}
// TODO(crbug.com/40236066) Move to PersistedTabData.onDeferredStartup
if (PriceTrackingFeatures.isPriceTrackingEligible(profile)) {
ShoppingPersistedTabData.onDeferredStartup();
}
});
}
private void initChannelsAsync() {
PostTask.postTask(
TaskTraits.BEST_EFFORT_MAY_BLOCK,
() -> ChannelsUpdater.getInstance().updateChannels());
}
private void initAsyncDiskTask() {
new AsyncTask<Void>() {
/**
* The threshold after which it's no longer appropriate to try to attach logcat output
* to a minidump file.
* Note: This threshold of 12 hours was chosen fairly imprecisely, based on the
* following intuition: On the one hand, Chrome can only access its own logcat output,
* so the most recent lines should be relevant when available. On a typical device,
* multiple hours of logcat output are available. On the other hand, it's important to
* provide an escape hatch in case the logcat extraction code itself crashes, as
* described in the doesCrashMinidumpNeedLogcat() documentation. Since this is a fairly
* small and relatively frequently-executed piece of code, crashes are expected to be
* unlikely; so it's okay for the escape hatch to be hard to use -- it's intended as an
* extreme last resort.
*/
private static final long LOGCAT_RELEVANCE_THRESHOLD_IN_HOURS = 12;
@Override
protected Void doInBackground() {
try {
TraceEvent.begin("ChromeBrowserInitializer.onDeferredStartup.doInBackground");
initCrashReporting();
// Initialize the WebappRegistry if it's not already initialized. Must be in
// async task due to shared preferences disk access on N.
WebappRegistry.getInstance();
// Force a widget refresh in order to wake up any possible zombie widgets.
// This is needed to ensure the right behavior when the process is suddenly
// killed.
BookmarkWidgetProvider.refreshAllWidgets();
removeSnapshotDatabase();
// Warm up all web app shared prefs. This must be run after the WebappRegistry
// instance is initialized.
WebappRegistry.warmUpSharedPrefs();
PackageMetrics.recordPackageStats();
return null;
} finally {
TraceEvent.end("ChromeBrowserInitializer.onDeferredStartup.doInBackground");
}
}
@Override
protected void onPostExecute(Void params) {
// Must be run on the UI thread after the WebappRegistry has been completely warmed.
WebappRegistry.getInstance().unregisterOldWebapps(System.currentTimeMillis());
}
/**
* Initializes the crash reporting system. More specifically, enables the crash
* reporting system if it is user-permitted, and initiates uploading of any pending
* crash reports. Also updates some UMA metrics and performs cleanup in the local crash
* minidump storage directory.
*/
private void initCrashReporting() {
// Crash reports can be uploaded as part of a background service even while the main
// Chrome activity is not running, and hence regular metrics reporting is not
// possible. Instead, metrics are temporarily written to prefs; export those prefs
// to UMA metrics here.
MinidumpUploadServiceImpl.storeBreakpadUploadStatsInUma(
CrashUploadCountStore.getInstance());
// Likewise, this is a good time to process and clean up any pending or stale crash
// reports left behind by previous runs.
CrashFileManager crashFileManager =
new CrashFileManager(ContextUtils.getApplicationContext().getCacheDir());
crashFileManager.cleanOutAllNonFreshMinidumpFiles();
// ANR collection is only available on R+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
crashFileManager.collectAndWriteAnrs();
}
// Next, identify any minidumps that lack logcat output, and are too old to add
// logcat output to. Mark these as ready for upload. If there is a fresh minidump
// that still needs logcat output to be attached, stash it for now.
File minidumpMissingLogcat = processMinidumpsSansLogcat(crashFileManager);
// Now, upload all pending crash reports that are not still in need of logcat data.
File[] minidumps =
crashFileManager.getMinidumpsReadyForUpload(
MinidumpUploadServiceImpl.MAX_TRIES_ALLOWED);
if (minidumps.length > 0) {
Log.i(
TAG,
"Attempting to upload %d accumulated crash dumps.",
minidumps.length);
MinidumpUploadServiceImpl.scheduleUploadJob();
}
// Finally, if there is a minidump that still needs logcat output to be attached, do
// so now. Note: It's important to do this strictly after calling
// |crashFileManager.getMinidumpsReadyForUpload()|. Otherwise, there is a race
// between appending the logcat and getting the results from that call, as the
// minidump will be renamed to be a valid file for upload upon logcat extraction
// success.
if (minidumpMissingLogcat != null) {
// Note: When using the JobScheduler API to schedule uploads, this call might
// result in a duplicate request to schedule minidump uploads -- if the call
// succeeds, and there were also other pending minidumps found above. This is
// fine; the job scheduler is robust to such duplicate calls.
AsyncTask.THREAD_POOL_EXECUTOR.execute(
new LogcatExtractionRunnable(minidumpMissingLogcat));
}
}
/**
* Process all pending minidump files that lack logcat output. As a simplifying
* assumption, assume that logcat output would only be relevant to the most recent
* pending minidump, if there are multiple. As of Chrome version 58, about 50% of
* startups that had *any* pending minidumps had at least one pending minidump without
* any logcat output. About 5% had multiple minidumps without any logcat output.
*
* TODO(isherman): This is the simplest approach to resolving the complexity of
* correctly attributing logcat output to the correct crash. However, it would be better
* to attach logcat output to each minidump file that lacks it, if the relevant output
* is still available. We can look at timestamps to correlate logcat lines with the
* minidumps they correspond to.
*
* @return A single fresh minidump that should have logcat attached to it, or null if no
* such minidump exists.
*/
private File processMinidumpsSansLogcat(CrashFileManager crashFileManager) {
File[] minidumpsSansLogcat = crashFileManager.getMinidumpsSansLogcat();
// If there are multiple minidumps present that are missing logcat output, only
// append it to the most recent one. Upload the rest as-is.
if (minidumpsSansLogcat.length > 1) {
for (int i = 1; i < minidumpsSansLogcat.length; ++i) {
CrashFileManager.trySetReadyForUpload(minidumpsSansLogcat[i]);
}
}
// Try to identify a single fresh minidump that should have logcat output appended
// to it.
if (minidumpsSansLogcat.length > 0) {
File mostRecentMinidumpSansLogcat = minidumpsSansLogcat[0];
if (doesCrashMinidumpNeedLogcat(mostRecentMinidumpSansLogcat)) {
return mostRecentMinidumpSansLogcat;
} else {
CrashFileManager.trySetReadyForUpload(mostRecentMinidumpSansLogcat);
}
}
return null;
}
/**
* Returns whether or not it's appropriate to try to extract recent logcat output and
* include that logcat output alongside the given {@param minidump} in a crash report.
* Logcat output should only be extracted if (a) it hasn't already been extracted for
* this minidump file, and (b) the minidump is fairly fresh. The freshness check is
* important for two reasons: (1) First of all, it helps avoid including irrelevant
* logcat output for a crash report. (2) Secondly, it provides an escape hatch that can
* help circumvent a possible infinite crash loop, if the code responsible for
* extracting and appending the logcat content is itself crashing. That is, the user can
* wait 12 hours prior to relaunching Chrome, at which point this potential crash loop
* would be circumvented.
* @return Whether to try to include logcat output in the crash report corresponding to
* the given minidump.
*/
private boolean doesCrashMinidumpNeedLogcat(File minidump) {
if (!CrashFileManager.isMinidumpSansLogcat(minidump.getName())) return false;
long ageInMillis = new Date().getTime() - minidump.lastModified();
long ageInHours = ageInMillis / DateUtils.HOUR_IN_MILLIS;
return ageInHours < LOGCAT_RELEVANCE_THRESHOLD_IN_HOURS;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Deletes the snapshot database which is no longer used because the feature has been removed
* in Chrome M41.
*/
@WorkerThread
private void removeSnapshotDatabase() {
synchronized (SNAPSHOT_DATABASE_LOCK) {
SharedPreferencesManager prefs = ChromeSharedPreferences.getInstance();
if (!prefs.readBoolean(ChromePreferenceKeys.SNAPSHOT_DATABASE_REMOVED, false)) {
ContextUtils.getApplicationContext().deleteDatabase(SNAPSHOT_DATABASE_NAME);
prefs.writeBoolean(ChromePreferenceKeys.SNAPSHOT_DATABASE_REMOVED, true);
}
}
}
private void startBindingManagementIfNeeded() {
// Moderate binding doesn't apply to low end devices.
if (SysUtils.isLowEndDevice()) return;
ChildProcessLauncherHelper.startBindingManagement(ContextUtils.getApplicationContext());
}
@SuppressWarnings("deprecation") // InputMethodSubtype.getLocale() deprecated in API 24
private void recordKeyboardLocaleUma() {
InputMethodManager imm =
(InputMethodManager)
ContextUtils.getApplicationContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> ims = imm.getEnabledInputMethodList();
ArrayList<String> uniqueLanguages = new ArrayList<>();
for (InputMethodInfo method : ims) {
List<InputMethodSubtype> submethods =
imm.getEnabledInputMethodSubtypeList(method, true);
for (InputMethodSubtype submethod : submethods) {
if (submethod.getMode().equals("keyboard")) {
String language = submethod.getLocale().split("_")[0];
if (!uniqueLanguages.contains(language)) {
uniqueLanguages.add(language);
}
}
}
}
RecordHistogram.recordCount1MHistogram("InputMethod.ActiveCount", uniqueLanguages.size());
}
}