// Copyright 2015 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.metrics;
import android.content.Intent;
import android.os.Handler;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram;
/**
* Keeps track of actions taken on startup.
*/
public class StartupMetrics {
// Actions we keep track of. Do not change indices, add new actions at the end only
// and update MAX_INDEX.
private static final int NO_ACTIVITY = 0;
private static final int OPENED_NTP = 1;
private static final int FOCUSED_OMNIBOX = 2;
private static final int OPENED_BOOKMARKS = 3;
private static final int OPENED_RECENTS = 4;
private static final int OPENED_HISTORY = 5;
private static final int OPENED_TAB_SWITCHER = 6;
private static final int MAX_INDEX = 7;
// Keeps track of the actions invoked in the first RECORDING_THRESHOLD_NS.
private int mFirstActionTaken = NO_ACTIVITY;
private boolean mIsMainIntent;
private Handler mHandler;
// This ensures metrics are recorded only once per updateIntent(...) call.
private boolean mShouldRecordHistogram;
// Startup time is measured by two different time sources.
// {@code mStartTimeNanoMonotonic} is measured from a monotonic time source with nanosecond
// precision whereas {@code mStartTimeMilli} is measured from the wall clock with millisecond
// precision. The monotonic time source may not persist across reboots whereas the wall clock
// is subject to change by the user.
private long mStartTimeNanoMonotonic;
private long mStartTimeMilli;
private static StartupMetrics sInstance;
// Record only the first 10s.
private static final long RECORDING_THRESHOLD_NS = 10000000000L;
private static final int MILLI_SEC_PER_MINUTE = 60000;
private static final int MINUTES_PER_30DAYS = 43200;
// Bucket sizes are exponential so we get minute level granularity for first 10 minutes.
private static final int NUM_BUCKETS = 50;
public static StartupMetrics getInstance() {
if (sInstance == null) {
sInstance = new StartupMetrics();
}
return sInstance;
}
// Singleton
private StartupMetrics() {
mHandler = new Handler();
}
/**
* Starts collecting metrics for the latest intent sent to DocumentActivity or
* ChromeTabbedActivity. This happens on every intent coming from launcher/external app
* for DocumentActivity and every time we get onStart() in ChromeTabbedActivity mode.
*/
public void updateIntent(Intent intent) {
mIsMainIntent = intent != null && Intent.ACTION_MAIN.equals(intent.getAction());
mFirstActionTaken = NO_ACTIVITY;
mStartTimeNanoMonotonic = System.nanoTime();
mStartTimeMilli = System.currentTimeMillis();
mShouldRecordHistogram = true;
}
private boolean isShortlyAfterChromeStarted() {
return (System.nanoTime() - mStartTimeNanoMonotonic) <= RECORDING_THRESHOLD_NS;
}
private void setFirstAction(int type) {
if (!isShortlyAfterChromeStarted() || mFirstActionTaken != NO_ACTIVITY) return;
mFirstActionTaken = type;
}
/** Records that the new tab page has been opened. */
public void recordOpenedNTP() {
setFirstAction(OPENED_NTP);
}
/** Records that the omnibox has been focused. */
public void recordFocusedOmnibox() {
setFirstAction(FOCUSED_OMNIBOX);
}
/** Records that the bookmarks page has been opened. */
public void recordOpenedBookmarks() {
setFirstAction(OPENED_BOOKMARKS);
}
/** Records that the recents page has been opened. */
public void recordOpenedRecents() {
setFirstAction(OPENED_RECENTS);
}
/** Records that the history page has been opened. */
public void recordOpenedHistory() {
setFirstAction(OPENED_HISTORY);
}
/** Records that the tab switcher has been accessed. */
public void recordOpenedTabSwitcher() {
setFirstAction(OPENED_TAB_SWITCHER);
}
/** Records the startup data in a histogram. Should only be called after native is loaded. */
public void recordHistogram(boolean onStop) {
if (!mShouldRecordHistogram) return;
if (!isShortlyAfterChromeStarted() || mFirstActionTaken != NO_ACTIVITY || onStop) {
String histogramName = mIsMainIntent ? "MobileStartup.MainIntentAction" :
"MobileStartup.NonMainIntentAction";
RecordHistogram.recordEnumeratedHistogram(histogramName, mFirstActionTaken, MAX_INDEX);
mShouldRecordHistogram = false;
long lastUsedTimeMilli = ContextUtils.getAppSharedPreferences().getLong(
UmaSessionStats.LAST_USED_TIME_PREF, 0);
if (mIsMainIntent && (lastUsedTimeMilli > 0) && (mStartTimeMilli > lastUsedTimeMilli)
&& (mStartTimeMilli - lastUsedTimeMilli > Integer.MAX_VALUE)) {
// Measured in minutes and capped at a day with a bucket precision of 6 minutes.
RecordHistogram.recordCustomCountHistogram("MobileStartup.TimeSinceLastUse",
(int) (mStartTimeMilli - lastUsedTimeMilli) / MILLI_SEC_PER_MINUTE, 1,
MINUTES_PER_30DAYS, NUM_BUCKETS);
}
} else {
// Call back later to record the histogram after 10s have elapsed.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
recordHistogram(false);
}
}, (RECORDING_THRESHOLD_NS - (System.nanoTime() - mStartTimeNanoMonotonic)) / 1000000);
}
}
}