blob: 532c41b1c53a3817cd59917c0d5f136796aa364d [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.SystemClock;
import android.support.annotation.WorkerThread;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.google.ipc.invalidation.external.client.android.service.AndroidLogger;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.PowerMonitor;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.AsyncTask;
import org.chromium.build.BuildHooksAndroid;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.AfterStartupTaskUtils;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ChromeActivitySessionTracker;
import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.ChromeBackupAgent;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.DefaultBrowserInfo;
import org.chromium.chrome.browser.DeferredStartupHandler;
import org.chromium.chrome.browser.DevToolsServer;
import org.chromium.chrome.browser.banners.AppBannerManager;
import org.chromium.chrome.browser.bookmarkswidget.BookmarkWidgetProvider;
import org.chromium.chrome.browser.contacts_picker.ContactsPickerDialog;
import org.chromium.chrome.browser.crash.LogcatExtractionRunnable;
import org.chromium.chrome.browser.crash.MinidumpUploadService;
import org.chromium.chrome.browser.download.DownloadController;
import org.chromium.chrome.browser.download.DownloadManagerService;
import org.chromium.chrome.browser.firstrun.ForcedSigninProcessor;
import org.chromium.chrome.browser.identity.UniqueIdentificationGeneratorFactory;
import org.chromium.chrome.browser.identity.UuidBasedUniqueIdentificationGenerator;
import org.chromium.chrome.browser.invalidation.UniqueIdInvalidationClientNameGenerator;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
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.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.notifications.channels.ChannelsUpdater;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
import org.chromium.chrome.browser.photo_picker.PhotoPickerDialog;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.profiles.ProfileManagerUtils;
import org.chromium.chrome.browser.rlz.RevenueStats;
import org.chromium.chrome.browser.searchwidget.SearchWidgetProvider;
import org.chromium.chrome.browser.services.GoogleServicesManager;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.sync.SyncController;
import org.chromium.chrome.browser.util.ConversionUtils;
import org.chromium.chrome.browser.webapps.WebApkVersionManager;
import org.chromium.chrome.browser.webapps.WebappRegistry;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.minidump_uploader.CrashFileManager;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.AccountsChangeObserver;
import org.chromium.content_public.browser.BrowserTaskExecutor;
import org.chromium.content_public.browser.ChildProcessLauncherHelper;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.printing.PrintDocumentAdapterWrapper;
import org.chromium.printing.PrintingControllerImpl;
import org.chromium.ui.ContactsPickerListener;
import org.chromium.ui.PhotoPickerListener;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.SelectFileDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
/**
* 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 SESSIONS_UUID_PREF_KEY = "chromium.sync.sessions.id";
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_REMOVED = "snapshot_database_removed";
private static final String SNAPSHOT_DATABASE_NAME = "snapshots.db";
private static ProcessInitializationHandler sInstance;
private boolean mInitializedPreNative;
private boolean mInitializedPostNative;
private boolean mInitializedDeferredStartupTasks;
private DevToolsServer mDevToolsServer;
/**
* @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 any 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 initializePreNative() {
try (TraceEvent e =
TraceEvent.scoped("ProcessInitializationHandler.initializePreNative()")) {
ThreadUtils.checkUiThread();
if (mInitializedPreNative) return;
handlePreNativeInitialization();
mInitializedPreNative = true;
}
}
/**
* Performs the shared class initialization.
*/
protected void handlePreNativeInitialization() {
BrowserTaskExecutor.register();
Context application = ContextUtils.getApplicationContext();
// Initialize the AccountManagerFacade with the correct AccountManagerDelegate. Must be done
// only once and before AccountMangerHelper.get(...) is called to avoid using the
// default AccountManagerDelegate.
AccountManagerFacade.initializeAccountManagerFacade(
AppHooks.get().createAccountManagerDelegate());
// Set the unique identification generator for invalidations. The
// invalidations system can start and attempt to fetch the client ID
// very early. We need this generator to be ready before that happens.
UniqueIdInvalidationClientNameGenerator.doInitializeAndInstallGenerator(application);
// Set minimum Tango log level. This sets an in-memory static field, and needs to be
// set in the ApplicationContext instead of an activity, since Tango can be woken up
// by the system directly though messages from GCM.
AndroidLogger.setMinimumAndroidLogLevel(Log.WARN);
// Set up the identification generator for sync. The ID is actually generated
// in the SyncController constructor.
UniqueIdentificationGeneratorFactory.registerGenerator(SyncController.GENERATOR_ID,
new UuidBasedUniqueIdentificationGenerator(
application, SESSIONS_UUID_PREF_KEY), false);
}
/**
* Initializes any dependencies that must occur after the native library has been loaded.
*/
public final void initializePostNative() {
ThreadUtils.checkUiThread();
if (mInitializedPostNative) return;
handlePostNativeInitialization();
mInitializedPostNative = true;
}
/**
* @return Whether post native initialization has been completed.
*/
public final boolean postNativeInitializationComplete() {
return mInitializedPostNative;
}
/**
* Performs the post native initialization.
*/
protected void handlePostNativeInitialization() {
final ChromeApplication application =
(ChromeApplication) ContextUtils.getApplicationContext();
DataReductionProxySettings.handlePostNativeInitialization();
ChromeActivitySessionTracker.getInstance().initializeWithNative();
ProfileManagerUtils.removeSessionCookiesForAllProfiles();
AppBannerManager.setAppDetailsDelegate(AppHooks.get().createAppDetailsDelegate());
ChromeLifetimeController.initialize();
PrefServiceBridge.getInstance().migratePreferences(application);
if (ChromeFeatureList.isEnabled(ChromeFeatureList.NEW_PHOTO_PICKER)) {
UiUtils.setPhotoPickerDelegate(new UiUtils.PhotoPickerDelegate() {
private PhotoPickerDialog mDialog;
@Override
public void showPhotoPicker(Context context, PhotoPickerListener listener,
boolean allowMultiple, List<String> mimeTypes) {
mDialog = new PhotoPickerDialog(context, listener, allowMultiple, mimeTypes);
mDialog.getWindow().getAttributes().windowAnimations =
R.style.PickerDialogAnimation;
mDialog.show();
}
@Override
public void onPhotoPickerDismissed() {
mDialog = null;
}
});
}
if (ChromeFeatureList.isEnabled(ChromeFeatureList.NEW_CONTACTS_PICKER)) {
UiUtils.setContactsPickerDelegate(new UiUtils.ContactsPickerDelegate() {
private ContactsPickerDialog mDialog;
@Override
public void showContactsPicker(Context context, ContactsPickerListener listener,
boolean allowMultiple, List<String> mimeTypes) {
mDialog = new ContactsPickerDialog(context, listener, allowMultiple, mimeTypes);
mDialog.getWindow().getAttributes().windowAnimations =
R.style.PickerDialogAnimation;
mDialog.show();
}
@Override
public void onContactsPickerDismissed() {
mDialog = null;
}
});
}
SearchWidgetProvider.initialize();
}
/**
* 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;
handleDeferredStartupTasksInitialization();
}
/**
* Performs the deferred startup task initialization.
*/
protected void handleDeferredStartupTasksInitialization() {
final ChromeApplication application =
(ChromeApplication) ContextUtils.getApplicationContext();
DeferredStartupHandler deferredStartupHandler = DeferredStartupHandler.getInstance();
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
// Punt all tasks that may block on disk off onto a background thread.
initAsyncDiskTask(application);
DefaultBrowserInfo.initBrowserFetcher();
AfterStartupTaskUtils.setStartupComplete();
PartnerBrowserCustomizations.setOnInitializeAsyncFinished(new Runnable() {
@Override
public void run() {
String homepageUrl = HomepageManager.getHomepageUri();
LaunchMetrics.recordHomePageLaunchMetrics(
HomepageManager.isHomepageEnabled(),
NewTabPage.isNTPUrl(homepageUrl), homepageUrl);
}
});
PowerMonitor.create();
ShareHelper.clearSharedImages();
SelectFileDialog.clearCapturedCameraFiles();
if (ChannelsUpdater.getInstance().shouldUpdateChannels()) {
initChannelsAsync();
}
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
// Clear any media notifications that existed when Chrome was last killed.
MediaCaptureNotificationService.clearMediaNotifications(application);
startModerateBindingManagementIfNeeded(application);
recordKeyboardLocaleUma(application);
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
LocaleManager.getInstance().recordStartupMetrics();
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
if (HomepageManager.shouldShowHomepageSetting()) {
RecordHistogram.recordBooleanHistogram("Settings.ShowHomeButtonPreferenceState",
HomepageManager.isHomepageEnabled());
RecordHistogram.recordBooleanHistogram("Settings.HomePageIsCustomized",
!HomepageManager.getInstance().getPrefHomepageUseDefaultUri());
}
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
// Starts syncing with GSA.
AppHooks.get().createGsaHelper().startSync();
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
// Record the saved restore state in a histogram
ChromeBackupAgent.recordRestoreHistogram();
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
ForcedSigninProcessor.start(application, null);
AccountManagerFacade.get().addObserver(
new AccountsChangeObserver() {
@Override
public void onAccountsChanged() {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
ForcedSigninProcessor.start(application, null);
}
});
}
});
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
GoogleServicesManager.get(application).onMainActivityStart();
RevenueStats.getInstance();
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
mDevToolsServer = new DevToolsServer(DEV_TOOLS_SERVER_SOCKET_PREFIX);
mDevToolsServer.setRemoteDebuggingEnabled(
true, DevToolsServer.Security.ALLOW_DEBUG_PERMISSION);
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
// Add process check to diagnose http://crbug.com/606309. Remove this after the bug
// is fixed.
assert !CommandLine.getInstance().hasSwitch(ContentSwitches.SWITCH_PROCESS_TYPE);
if (!CommandLine.getInstance().hasSwitch(ContentSwitches.SWITCH_PROCESS_TYPE)) {
DownloadController.setDownloadNotificationService(
DownloadManagerService.getDownloadManagerService());
}
if (ApiCompatibilityUtils.isPrintingSupported()) {
String errorText = application.getResources().getString(
R.string.error_printing_failed);
PrintingControllerImpl.create(new PrintDocumentAdapterWrapper(), errorText);
}
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
BackgroundTaskSchedulerFactory.getScheduler().checkForOSUpgrade(application);
}
});
deferredStartupHandler.addDeferredTask(new Runnable() {
@Override
public void run() {
logEGLShaderCacheSizeHistogram();
}
});
deferredStartupHandler.addDeferredTask(
() -> { BuildHooksAndroid.maybeRecordResourceMetrics(); });
deferredStartupHandler.addDeferredTask(() -> {
MediaViewerUtils.updateMediaLauncherActivityEnabled(
ContextUtils.getApplicationContext());
});
}
private void initChannelsAsync() {
new AsyncTask<Void>() {
@Override
protected Void doInBackground() {
ChannelsUpdater.getInstance().updateChannels();
return null;
}
}
.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
private void initAsyncDiskTask(final Context context) {
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;
private long mAsyncTaskStartTime;
@Override
protected Void doInBackground() {
try {
TraceEvent.begin("ChromeBrowserInitializer.onDeferredStartup.doInBackground");
mAsyncTaskStartTime = SystemClock.uptimeMillis();
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(context);
WebApkVersionManager.updateWebApksIfNeeded();
removeSnapshotDatabase(context);
// 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());
RecordHistogram.recordLongTimesHistogram(
"UMA.Debug.EnableCrashUpload.DeferredStartUpAsyncTaskDuration",
SystemClock.uptimeMillis() - mAsyncTaskStartTime, TimeUnit.MILLISECONDS);
}
/**
* 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.
MinidumpUploadService.storeBreakpadUploadStatsInUma(
ChromePreferenceManager.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();
// 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(
MinidumpUploadService.MAX_TRIES_ALLOWED);
if (minidumps.length > 0) {
Log.i(TAG, "Attempting to upload %d accumulated crash dumps.",
minidumps.length);
if (MinidumpUploadService.shouldUseJobSchedulerForUploads()) {
MinidumpUploadService.scheduleUploadJob();
} else {
MinidumpUploadService.tryUploadAllCrashDumps();
}
}
// 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 = TimeUnit.HOURS.convert(ageInMillis, TimeUnit.MILLISECONDS);
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(Context context) {
synchronized (SNAPSHOT_DATABASE_LOCK) {
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
if (!prefs.getBoolean(SNAPSHOT_DATABASE_REMOVED, false)) {
context.deleteDatabase(SNAPSHOT_DATABASE_NAME);
prefs.edit().putBoolean(SNAPSHOT_DATABASE_REMOVED, true).apply();
}
}
}
private void startModerateBindingManagementIfNeeded(Context context) {
// Moderate binding doesn't apply to low end devices.
if (SysUtils.isLowEndDevice()) return;
ChildProcessLauncherHelper.startModerateBindingManagement(context);
}
@SuppressWarnings("deprecation") // InputMethodSubtype.getLocale() deprecated in API 24
private void recordKeyboardLocaleUma(Context context) {
InputMethodManager imm =
(InputMethodManager) context.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.recordCountHistogram("InputMethod.ActiveCount", uniqueLanguages.size());
InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
Locale systemLocale = Locale.getDefault();
if (currentSubtype != null && currentSubtype.getLocale() != null && systemLocale != null) {
String keyboardLanguage = currentSubtype.getLocale().split("_")[0];
boolean match = systemLocale.getLanguage().equalsIgnoreCase(keyboardLanguage);
RecordHistogram.recordBooleanHistogram("InputMethod.MatchesSystemLanguage", match);
}
}
/**
* Logs a histogram with the size of the Android EGL shader cache.
*/
@TargetApi(Build.VERSION_CODES.N)
private static void logEGLShaderCacheSizeHistogram() {
// To simplify logic, only log this value on Android N+.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return;
}
final Context cacheContext =
ContextUtils.getApplicationContext().createDeviceProtectedStorageContext();
// Must log async, as we're doing a file access.
new AsyncTask<Void>() {
// Record file sizes between 1-2560KB. Expected range is 1-2048KB, so this gives
// us a bit of buffer. These values cannot be changed, as doing so will alter
// histogram bucketing and confuse the dashboard.
private static final int MIN_CACHE_FILE_SIZE_KB = 1;
private static final int MAX_CACHE_FILE_SIZE_KB = 2560;
@Override
protected Void doInBackground() {
File codeCacheDir = cacheContext.getCodeCacheDir();
if (codeCacheDir == null) {
return null;
}
// This filename is defined in core/java/android/view/HardwareRenderer.java,
// and has been located in the codeCacheDir since Android M.
File cacheFile = new File(codeCacheDir, "com.android.opengl.shaders_cache");
if (!cacheFile.exists()) {
return null;
}
long cacheFileSizeKb = ConversionUtils.bytesToKilobytes(cacheFile.length());
// Clamp size to [minFileSizeKb, maxFileSizeKb). This also guarantees that the
// int-cast below is safe.
if (cacheFileSizeKb < MIN_CACHE_FILE_SIZE_KB) {
cacheFileSizeKb = MIN_CACHE_FILE_SIZE_KB;
}
if (cacheFileSizeKb >= MAX_CACHE_FILE_SIZE_KB) {
cacheFileSizeKb = MAX_CACHE_FILE_SIZE_KB - 1;
}
String histogramName = "Memory.Experimental.Browser.EGLShaderCacheSize.Android";
RecordHistogram.recordCustomCountHistogram(histogramName, (int) cacheFileSizeKb,
MIN_CACHE_FILE_SIZE_KB, MAX_CACHE_FILE_SIZE_KB, 50);
return null;
}
}
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}