blob: fddc1e0a0fc24be67ef8259d6ce7f9aab7e81e25 [file] [log] [blame]
// Copyright 2018 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.
#include "ash/metrics/demo_session_metrics_recorder.h"
#include <string>
#include <utility>
#include "ash/public/cpp/app_types.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shelf/shelf_window_watcher.h"
#include "ash/shell.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "extensions/common/constants.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/window_types.h"
#include "ui/aura/window.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
using DemoModeApp = DemoSessionMetricsRecorder::DemoModeApp;
// How often to sample.
constexpr auto kSamplePeriod = base::TimeDelta::FromSeconds(1);
// How many periods to wait for user activity before discarding samples.
// This timeout is low because demo sessions tend to be very short. If we
// recorded samples for a full minute while the device is in between uses, we
// would bias our measurements toward whatever app was used last.
constexpr int kMaxPeriodsWithoutActivity =
base::TimeDelta::FromSeconds(15) / kSamplePeriod;
// Maps a Chrome app ID to a DemoModeApp value for metrics.
DemoModeApp GetAppFromAppId(const std::string& app_id) {
// Each version of the Highlights app is bucketed into the same value.
if (app_id == extension_misc::kHighlightsAppId ||
app_id == extension_misc::kHighlightsAlt1AppId ||
app_id == extension_misc::kHighlightsAlt2AppId) {
return DemoModeApp::kHighlights;
if (app_id == extension_misc::kCameraAppId)
return DemoModeApp::kCamera;
if (app_id == extension_misc::kFilesManagerAppId)
return DemoModeApp::kFiles;
if (app_id == extension_misc::kGeniusAppId)
return DemoModeApp::kGetHelp;
if (app_id == extension_misc::kGoogleKeepAppId)
return DemoModeApp::kGoogleKeep;
if (app_id == extensions::kWebStoreAppId)
return DemoModeApp::kWebStore;
if (app_id == extension_misc::kYoutubeAppId)
return DemoModeApp::kYouTube;
return DemoModeApp::kOtherChromeApp;
// Maps an ARC++ package name to a DemoModeApp value for metrics.
DemoModeApp GetAppFromPackageName(const std::string& package_name) {
// Google apps.
if (package_name == "")
return DemoModeApp::kGooglePhotos;
if (package_name == "")
return DemoModeApp::kGoogleSheets;
if (package_name == "")
return DemoModeApp::kGoogleSlides;
if (package_name == "")
return DemoModeApp::kPlayStore;
// Third-party apps.
if (package_name == "")
return DemoModeApp::kAsphalt8;
if (package_name == "com.brakefield.painter")
return DemoModeApp::kInfinitePainter;
if (package_name == "com.myscript.nebo.demo")
return DemoModeApp::kMyScriptNebo;
if (package_name == "")
return DemoModeApp::kSquid;
return DemoModeApp::kOtherArcApp;
// Maps the app-like thing in |window| to a DemoModeApp value for metrics.
DemoModeApp GetAppFromWindow(const aura::Window* window) {
ash::AppType app_type =
if (app_type == ash::AppType::ARC_APP) {
// The ShelfID app id isn't used to identify ARC++ apps since it's a hash of
// both the package name and the activity.
const std::string* package_name =
return package_name ? GetAppFromPackageName(*package_name)
: DemoModeApp::kOtherArcApp;
std::string app_id =
// The Chrome "app" in the shelf is just the browser.
if (app_id == extension_misc::kChromeAppId)
return DemoModeApp::kBrowser;
auto is_default = [](const std::string& app_id) {
if (!features::IsUsingWindowService())
return app_id.empty();
return base::StartsWith(app_id, ShelfWindowWatcher::kDefaultShelfIdPrefix,
// If the window is the "browser" type, having an app ID other than the
// default indicates a hosted/bookmark app.
if (app_type == ash::AppType::CHROME_APP ||
(app_type == ash::AppType::BROWSER && !is_default(app_id))) {
return GetAppFromAppId(app_id);
if (app_type == ash::AppType::BROWSER)
return DemoModeApp::kBrowser;
return DemoModeApp::kOtherWindow;
} // namespace
std::unique_ptr<base::RepeatingTimer> timer)
: timer_(std::move(timer)), observer_(this) {
// Outside of tests, use a normal repeating timer.
if (!timer_.get())
timer_ = std::make_unique<base::RepeatingTimer>();
DemoSessionMetricsRecorder::~DemoSessionMetricsRecorder() {
// Report any remaining stored samples on exit. (If the user went idle, there
// won't be any.)
void DemoSessionMetricsRecorder::OnUserActivity(const ui::Event* event) {
// Report samples recorded since the last activity.
// Restart the timer if the device has been idle.
if (!timer_->IsRunning())
periods_since_activity_ = 0;
void DemoSessionMetricsRecorder::StartRecording() {
timer_->Start(FROM_HERE, kSamplePeriod, this,
void DemoSessionMetricsRecorder::TakeSampleOrPause() {
// After enough inactive time, assume the user left.
if (++periods_since_activity_ > kMaxPeriodsWithoutActivity) {
// These samples were collected since the last user activity.
const aura::Window* window =
if (!window)
DemoModeApp app = window->type() == aura::client::WINDOW_TYPE_NORMAL
? GetAppFromWindow(window)
: DemoModeApp::kOtherWindow;
void DemoSessionMetricsRecorder::ReportSamples() {
for (DemoModeApp app : unreported_samples_)
} // namespace ash