Commit 0c9fc2f3 authored by Danan S's avatar Danan S Committed by Commit Bot

Add UMA metric to count unique apps launched during Demo Mode

In the process, refactor some of the shared window-property-fetching
code from the existing metric so it can be used for this one.

Bug: 904564
Change-Id: I1e50213f12171a7c2282c920a0e1af906f18af48
Reviewed-on: https://chromium-review.googlesource.com/c/1427790
Commit-Queue: Danan S <danan@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/master@{#626871}
parent 5d95bcb1
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "ash/metrics/demo_session_metrics_recorder.h" #include "ash/metrics/demo_session_metrics_recorder.h"
#include <iostream>
#include <string> #include <string>
#include <utility> #include <utility>
...@@ -12,6 +13,7 @@ ...@@ -12,6 +13,7 @@
#include "ash/shelf/shelf_window_watcher.h" #include "ash/shelf/shelf_window_watcher.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/scoped_observer.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "extensions/common/constants.h" #include "extensions/common/constants.h"
...@@ -20,6 +22,7 @@ ...@@ -20,6 +22,7 @@
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/base/ui_base_features.h" #include "ui/base/ui_base_features.h"
#include "ui/base/user_activity/user_activity_detector.h" #include "ui/base/user_activity/user_activity_detector.h"
#include "ui/wm/core/focus_controller.h"
#include "ui/wm/public/activation_client.h" #include "ui/wm/public/activation_client.h"
namespace ash { namespace ash {
...@@ -46,6 +49,13 @@ DemoModeApp GetAppFromAppId(const std::string& app_id) { ...@@ -46,6 +49,13 @@ DemoModeApp GetAppFromAppId(const std::string& app_id) {
return DemoModeApp::kHighlights; return DemoModeApp::kHighlights;
} }
// Each version of the Screensaver app is bucketed into the same value.
if (app_id == extension_misc::kScreensaverAppId ||
app_id == extension_misc::kScreensaverAlt1AppId ||
app_id == extension_misc::kScreensaverAlt2AppId) {
return DemoModeApp::kScreensaver;
}
if (app_id == extension_misc::kCameraAppId) if (app_id == extension_misc::kCameraAppId)
return DemoModeApp::kCamera; return DemoModeApp::kCamera;
if (app_id == extension_misc::kFilesManagerAppId) if (app_id == extension_misc::kFilesManagerAppId)
...@@ -86,21 +96,30 @@ DemoModeApp GetAppFromPackageName(const std::string& package_name) { ...@@ -86,21 +96,30 @@ DemoModeApp GetAppFromPackageName(const std::string& package_name) {
return DemoModeApp::kOtherArcApp; return DemoModeApp::kOtherArcApp;
} }
const std::string* GetArcPackageName(const aura::Window* window) {
return window->GetProperty(kArcPackageNameKey);
}
const ShelfID GetShelfID(const aura::Window* window) {
return ShelfID::Deserialize(window->GetProperty(kShelfIDKey));
}
AppType GetAppType(const aura::Window* window) {
return static_cast<AppType>(window->GetProperty(aura::client::kAppType));
}
// Maps the app-like thing in |window| to a DemoModeApp value for metrics. // Maps the app-like thing in |window| to a DemoModeApp value for metrics.
DemoModeApp GetAppFromWindow(const aura::Window* window) { DemoModeApp GetAppFromWindow(const aura::Window* window) {
ash::AppType app_type = AppType app_type = GetAppType(window);
static_cast<ash::AppType>(window->GetProperty(aura::client::kAppType)); if (app_type == AppType::ARC_APP) {
if (app_type == ash::AppType::ARC_APP) {
// The ShelfID app id isn't used to identify ARC++ apps since it's a hash of // The ShelfID app id isn't used to identify ARC++ apps since it's a hash of
// both the package name and the activity. // both the package name and the activity.
const std::string* package_name = const std::string* package_name = GetArcPackageName(window);
static_cast<std::string*>(window->GetProperty(kArcPackageNameKey));
return package_name ? GetAppFromPackageName(*package_name) return package_name ? GetAppFromPackageName(*package_name)
: DemoModeApp::kOtherArcApp; : DemoModeApp::kOtherArcApp;
} }
std::string app_id = std::string app_id = GetShelfID(window).app_id;
ShelfID::Deserialize(window->GetProperty(kShelfIDKey)).app_id;
// The Chrome "app" in the shelf is just the browser. // The Chrome "app" in the shelf is just the browser.
if (app_id == extension_misc::kChromeAppId) if (app_id == extension_misc::kChromeAppId)
...@@ -116,33 +135,141 @@ DemoModeApp GetAppFromWindow(const aura::Window* window) { ...@@ -116,33 +135,141 @@ DemoModeApp GetAppFromWindow(const aura::Window* window) {
// If the window is the "browser" type, having an app ID other than the // If the window is the "browser" type, having an app ID other than the
// default indicates a hosted/bookmark app. // default indicates a hosted/bookmark app.
if (app_type == ash::AppType::CHROME_APP || if (app_type == AppType::CHROME_APP ||
(app_type == ash::AppType::BROWSER && !is_default(app_id))) { (app_type == AppType::BROWSER && !is_default(app_id))) {
return GetAppFromAppId(app_id); return GetAppFromAppId(app_id);
} }
if (app_type == ash::AppType::BROWSER) if (app_type == AppType::BROWSER)
return DemoModeApp::kBrowser; return DemoModeApp::kBrowser;
return DemoModeApp::kOtherWindow; return DemoModeApp::kOtherWindow;
} }
} // namespace } // namespace
// Observes for changes in a window's ArcPackageName property for the purpose of
// logging of unique launches of ARC apps.
class DemoSessionMetricsRecorder::UniqueAppsLaunchedArcPackageNameObserver
: public aura::WindowObserver {
public:
explicit UniqueAppsLaunchedArcPackageNameObserver(
DemoSessionMetricsRecorder* metrics_recorder)
: metrics_recorder_(metrics_recorder), scoped_observer_(this) {}
// aura::WindowObserver
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) override {
if (key != kArcPackageNameKey)
return;
const std::string* package_name = GetArcPackageName(window);
if (package_name != nullptr)
metrics_recorder_->RecordAppLaunch(*package_name);
else
VLOG(1) << "Got null ARC package name";
window->RemoveObserver(this);
}
void ObserveWindow(aura::Window* window) { scoped_observer_.Add(window); }
void OnWindowDestroyed(aura::Window* window) override {
window->RemoveObserver(this);
}
private:
DemoSessionMetricsRecorder* metrics_recorder_;
ScopedObserver<aura::Window, UniqueAppsLaunchedArcPackageNameObserver>
scoped_observer_;
DISALLOW_COPY_AND_ASSIGN(UniqueAppsLaunchedArcPackageNameObserver);
};
DemoSessionMetricsRecorder::DemoSessionMetricsRecorder( DemoSessionMetricsRecorder::DemoSessionMetricsRecorder(
std::unique_ptr<base::RepeatingTimer> timer) std::unique_ptr<base::RepeatingTimer> timer)
: timer_(std::move(timer)), observer_(this) { : timer_(std::move(timer)),
observer_(this),
unique_apps_arc_package_name_observer_(
std::make_unique<UniqueAppsLaunchedArcPackageNameObserver>(this)) {
// Outside of tests, use a normal repeating timer. // Outside of tests, use a normal repeating timer.
if (!timer_.get()) if (!timer_.get())
timer_ = std::make_unique<base::RepeatingTimer>(); timer_ = std::make_unique<base::RepeatingTimer>();
StartRecording(); StartRecording();
observer_.Add(ui::UserActivityDetector::Get()); observer_.Add(ui::UserActivityDetector::Get());
// Subscribe to window activation updates. Even though this gets us
// notifications for all window activations, we ignore the ARC
// notifications because they don't contain the app_id. We handle
// accounting for ARC windows with OnTaskCreated.
if (Shell::Get()->GetPrimaryRootWindow()) {
activation_client_ = Shell::Get()->focus_controller();
activation_client_->AddObserver(this);
}
} }
DemoSessionMetricsRecorder::~DemoSessionMetricsRecorder() { DemoSessionMetricsRecorder::~DemoSessionMetricsRecorder() {
// Report any remaining stored samples on exit. (If the user went idle, there // Report any remaining stored samples on exit. (If the user went idle, there
// won't be any.) // won't be any.)
ReportSamples(); ReportSamples();
// Unsubscribe from window activation events.
activation_client_->RemoveObserver(this);
ReportUniqueAppsLaunched();
}
// This method will only record 1 launch for each unique app_id, regardless of
// how many times it is called with that app_id.
void DemoSessionMetricsRecorder::RecordAppLaunch(const std::string& app_id) {
if (unique_apps_launched_recording_enabled_ &&
GetAppFromAppId(app_id) != DemoModeApp::kHighlights &&
GetAppFromAppId(app_id) != DemoModeApp::kScreensaver) {
unique_apps_launched_.insert(app_id);
}
}
void DemoSessionMetricsRecorder::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!gained_active)
return;
// Don't count popup windows.
if (gained_active->type() != aura::client::WINDOW_TYPE_NORMAL)
return;
AppType app_type = GetAppType(gained_active);
std::string app_id;
if (app_type == AppType::ARC_APP) {
const std::string* package_name = GetArcPackageName(gained_active);
if (!package_name) {
// The package name property for the window has not been set yet.
// Listen for changes to the window properties so we can
// be informed when the package name gets set.
if (!gained_active->HasObserver(
unique_apps_arc_package_name_observer_.get())) {
unique_apps_arc_package_name_observer_->ObserveWindow(gained_active);
}
return;
}
app_id = *package_name;
} else {
// This is a non-ARC window, so we just get the shelf ID, which should
// be unique per app.
app_id = GetShelfID(gained_active).app_id;
}
// Some app_ids are empty, i.e the "You will be signed out
// in X seconds" modal dialog in Demo Mode, so skip those.
if (app_id.empty())
return;
RecordAppLaunch(app_id);
} }
void DemoSessionMetricsRecorder::OnUserActivity(const ui::Event* event) { void DemoSessionMetricsRecorder::OnUserActivity(const ui::Event* event) {
...@@ -156,6 +283,7 @@ void DemoSessionMetricsRecorder::OnUserActivity(const ui::Event* event) { ...@@ -156,6 +283,7 @@ void DemoSessionMetricsRecorder::OnUserActivity(const ui::Event* event) {
} }
void DemoSessionMetricsRecorder::StartRecording() { void DemoSessionMetricsRecorder::StartRecording() {
unique_apps_launched_recording_enabled_ = true;
timer_->Start(FROM_HERE, kSamplePeriod, this, timer_->Start(FROM_HERE, kSamplePeriod, this,
&DemoSessionMetricsRecorder::TakeSampleOrPause); &DemoSessionMetricsRecorder::TakeSampleOrPause);
} }
...@@ -170,7 +298,7 @@ void DemoSessionMetricsRecorder::TakeSampleOrPause() { ...@@ -170,7 +298,7 @@ void DemoSessionMetricsRecorder::TakeSampleOrPause() {
} }
const aura::Window* window = const aura::Window* window =
ash::Shell::Get()->activation_client()->GetActiveWindow(); Shell::Get()->activation_client()->GetActiveWindow();
if (!window) if (!window)
return; return;
...@@ -186,4 +314,12 @@ void DemoSessionMetricsRecorder::ReportSamples() { ...@@ -186,4 +314,12 @@ void DemoSessionMetricsRecorder::ReportSamples() {
unreported_samples_.clear(); unreported_samples_.clear();
} }
void DemoSessionMetricsRecorder::ReportUniqueAppsLaunched() {
if (unique_apps_launched_recording_enabled_)
UMA_HISTOGRAM_COUNTS_100("DemoMode.UniqueAppsLaunched",
unique_apps_launched_.size());
unique_apps_launched_.clear();
}
} // namespace ash } // namespace ash
...@@ -6,12 +6,17 @@ ...@@ -6,12 +6,17 @@
#define ASH_METRICS_DEMO_SESSION_METRICS_RECORDER_H_ #define ASH_METRICS_DEMO_SESSION_METRICS_RECORDER_H_
#include <memory> #include <memory>
#include <string>
#include <vector> #include <vector>
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "base/containers/flat_set.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "ui/aura/window_observer.h"
#include "ui/base/user_activity/user_activity_observer.h" #include "ui/base/user_activity/user_activity_observer.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/activation_client.h"
namespace base { namespace base {
class RepeatingTimer; class RepeatingTimer;
...@@ -25,7 +30,9 @@ namespace ash { ...@@ -25,7 +30,9 @@ namespace ash {
// A metrics recorder for demo sessions that samples the active window's app or // A metrics recorder for demo sessions that samples the active window's app or
// window type. Only used when the device is in Demo Mode. // window type. Only used when the device is in Demo Mode.
class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver { class ASH_EXPORT DemoSessionMetricsRecorder
: public ui::UserActivityObserver,
public wm::ActivationChangeObserver {
public: public:
// These apps are preinstalled in Demo Mode. This list is not exhaustive, and // These apps are preinstalled in Demo Mode. This list is not exhaustive, and
// includes first- and third-party Chrome and ARC apps. // includes first- and third-party Chrome and ARC apps.
...@@ -52,9 +59,10 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver { ...@@ -52,9 +59,10 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver {
kSquid = 16, // Android note-taking app. kSquid = 16, // Android note-taking app.
kWebStore = 17, kWebStore = 17,
kYouTube = 18, kYouTube = 18,
kScreensaver = 19, // Demo Mode screensaver app.
// Add future entries above this comment, in sync with enums.xml. // Add future entries above this comment, in sync with enums.xml.
// Update kMaxValue to the last value. // Update kMaxValue to the last value.
kMaxValue = kYouTube, kMaxValue = kScreensaver,
}; };
// The recorder will create a normal timer by default. Tests should provide a // The recorder will create a normal timer by default. Tests should provide a
...@@ -66,6 +74,11 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver { ...@@ -66,6 +74,11 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver {
// ui::UserActivityObserver: // ui::UserActivityObserver:
void OnUserActivity(const ui::Event* event) override; void OnUserActivity(const ui::Event* event) override;
// wm::ActivationChangeObserver:
void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) override;
private: private:
// Starts the timer for periodic sampling. // Starts the timer for periodic sampling.
void StartRecording(); void StartRecording();
...@@ -77,10 +90,30 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver { ...@@ -77,10 +90,30 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver {
// Emits histograms for recorded samples. // Emits histograms for recorded samples.
void ReportSamples(); void ReportSamples();
// Indicates whether the specified app_id should be recorded for
// the unique-apps-launched stat.
bool ShouldRecordAppLaunch(const std::string& app_id);
// Records the specified app_id's launch, subject to the
// restrictions of ShouldRecordAppLaunch().
void RecordAppLaunch(const std::string& app_id);
// Emits histograms for the number of unique apps launched.
void ReportUniqueAppsLaunched();
// Stores samples as they are collected. Report to UMA if we see user // Stores samples as they are collected. Report to UMA if we see user
// activity soon after. Guaranteed not to grow too large. // activity soon after. Guaranteed not to grow too large.
std::vector<DemoModeApp> unreported_samples_; std::vector<DemoModeApp> unreported_samples_;
// Indicates whether the unique-app-launch stats recording has been enabled.
bool unique_apps_launched_recording_enabled_ = false;
// Tracks the ids of apps that have been launched in Demo Mode.
base::flat_set<std::string> unique_apps_launched_;
// Used for subscribing to window activation events.
wm::ActivationClient* activation_client_ = nullptr;
// How many periods have elapsed since the last user activity. // How many periods have elapsed since the last user activity.
int periods_since_activity_ = 0; int periods_since_activity_ = 0;
...@@ -89,9 +122,14 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver { ...@@ -89,9 +122,14 @@ class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver {
ScopedObserver<ui::UserActivityDetector, DemoSessionMetricsRecorder> ScopedObserver<ui::UserActivityDetector, DemoSessionMetricsRecorder>
observer_; observer_;
class UniqueAppsLaunchedArcPackageNameObserver;
std::unique_ptr<UniqueAppsLaunchedArcPackageNameObserver>
unique_apps_arc_package_name_observer_;
DISALLOW_COPY_AND_ASSIGN(DemoSessionMetricsRecorder); DISALLOW_COPY_AND_ASSIGN(DemoSessionMetricsRecorder);
}; };
} // namespace ash } // namespace ash
#endif // ASH_METRICS_POINTER_METRICS_RECORDER_H_ #endif // ASH_METRICS_DEMO_SESSION_METRICS_RECORDER_H_
...@@ -129,7 +129,8 @@ class DemoSessionMetricsRecorderTest : public AshTestBase { ...@@ -129,7 +129,8 @@ class DemoSessionMetricsRecorderTest : public AshTestBase {
window->SetProperty( window->SetProperty(
kShelfIDKey, kShelfIDKey,
new std::string(ShelfID(app_id, std::string()).Serialize())); new std::string(ShelfID(app_id, std::string()).Serialize()));
window->SetProperty(kArcPackageNameKey, new std::string(package_name)); if (!package_name.empty())
window->SetProperty(kArcPackageNameKey, new std::string(package_name));
return window; return window;
} }
...@@ -445,5 +446,79 @@ TEST_F(DemoSessionMetricsRecorderTest, IgnoreOnIdleSession) { ...@@ -445,5 +446,79 @@ TEST_F(DemoSessionMetricsRecorderTest, IgnoreOnIdleSession) {
histogram_tester_->ExpectTotalCount("DemoMode.ActiveApp", 0); histogram_tester_->ExpectTotalCount("DemoMode.ActiveApp", 0);
} }
TEST_F(DemoSessionMetricsRecorderTest, UniqueAppsLaunchedOnDeletion) {
// Activate each window twice. Despite activating each twice,
// the count should only be incremented once per unique app.
auto chrome_app_window = CreateChromeAppWindow(extension_misc::kCameraAppId);
wm::ActivateWindow(chrome_app_window.get());
wm::DeactivateWindow(chrome_app_window.get());
wm::ActivateWindow(chrome_app_window.get());
auto chrome_browser_window =
CreateChromeAppWindow(extension_misc::kChromeAppId);
wm::ActivateWindow(chrome_browser_window.get());
wm::DeactivateWindow(chrome_browser_window.get());
wm::ActivateWindow(chrome_browser_window.get());
auto arc_window_1 = CreateArcWindow("com.google.Photos");
wm::ActivateWindow(arc_window_1.get());
wm::DeactivateWindow(arc_window_1.get());
wm::ActivateWindow(arc_window_1.get());
auto arc_window_2 = CreateArcWindow("com.google.Maps");
wm::ActivateWindow(arc_window_2.get());
wm::DeactivateWindow(arc_window_2.get());
wm::ActivateWindow(arc_window_2.get());
// Popup windows shouldn't be counted at all.
auto popup_window = CreatePopupWindow();
wm::ActivateWindow(popup_window.get());
wm::DeactivateWindow(popup_window.get());
wm::ActivateWindow(popup_window.get());
DeleteMetricsRecorder();
histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 4, 1);
}
TEST_F(DemoSessionMetricsRecorderTest,
NoUniqueAppsLaunchedOnMissingArcPackageName) {
// Create an ARC window with no package name set yet
auto arc_window_1 = CreateArcWindow("");
wm::ActivateWindow(arc_window_1.get());
DeleteMetricsRecorder();
// There shuld be no unique apps reported if there was no package name.
histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 0, 1);
}
TEST_F(DemoSessionMetricsRecorderTest,
UniqueAppsLaunchedOnDelayedArcPackageName) {
// Create an ARC window with no package name set yet.
auto arc_window_1 = CreateArcWindow("");
wm::ActivateWindow(arc_window_1.get());
// Set the package name after window creation/activation.
arc_window_1->SetProperty(kArcPackageNameKey,
new std::string("com.google.Photos"));
auto arc_window_2 = CreateArcWindow("com.google.Maps");
wm::ActivateWindow(arc_window_2.get());
DeleteMetricsRecorder();
// There should be 2 unique apps reported.
histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 2, 1);
}
TEST_F(DemoSessionMetricsRecorderTest, NoUniqueAppsLaunchedOnDeletion) {
DeleteMetricsRecorder();
// There should be no samples if the recorder is deleted with 0 unique apps
// launched.
histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 0, 1);
}
} // namespace } // namespace
} // namespace ash } // namespace ash
...@@ -10994,6 +10994,7 @@ Called by update_net_error_codes.py.--> ...@@ -10994,6 +10994,7 @@ Called by update_net_error_codes.py.-->
<int value="16" label="Squid"/> <int value="16" label="Squid"/>
<int value="17" label="Web Store"/> <int value="17" label="Web Store"/>
<int value="18" label="YouTube"/> <int value="18" label="YouTube"/>
<int value="19" label="Screensaver"/>
</enum> </enum>
<enum name="DemoModeIdleLogoutWarningEvent"> <enum name="DemoModeIdleLogoutWarningEvent">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment