Commit 5220a230 authored by Glenn Hartmann's avatar Glenn Hartmann Committed by Commit Bot

Record WebAPK Session Duration UKM metric.

This records WebAPK Session Duration to UKM, mirroring
the existing UMA metric of the same purpose.

TBR=msramek@chromium.org

Bug: 982362
Change-Id: I9deb8be6f8df3908268c6d03f33e27a4c0fd731a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1710501
Commit-Queue: Glenn Hartmann <hartmanng@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Reviewed-by: default avatarPeter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#681115}
parent 303dfe2c
......@@ -2626,6 +2626,7 @@ generate_jni("chrome_jni_headers") {
"java/src/org/chromium/chrome/browser/webapps/WebApkInstallService.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkInstaller.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkPostShareTargetNavigator.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUkmRecorder.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java",
"java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java",
......
......@@ -1689,6 +1689,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/WebApkServiceClient.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkShareTargetUtil.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkSplashNetworkErrorObserver.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUkmRecorder.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java",
......
......@@ -27,7 +27,7 @@ public class WebApkActivity extends WebappActivity {
/** Manages whether to check update for the WebAPK, and starts update check if needed. */
private WebApkUpdateManager mUpdateManager;
/** The start time that the activity becomes focused. */
/** The start time that the activity becomes focused in milliseconds since boot. */
private long mStartTime;
private static final String TAG = "cr_WebApkActivity";
......@@ -110,8 +110,10 @@ public class WebApkActivity extends WebappActivity {
@Override
public void onPauseWithNative() {
WebApkInfo info = (WebApkInfo) getWebappInfo();
WebApkUma.recordWebApkSessionDuration(
info.distributor(), SystemClock.elapsedRealtime() - mStartTime);
long sessionDuration = SystemClock.elapsedRealtime() - mStartTime;
WebApkUma.recordWebApkSessionDuration(info.distributor(), sessionDuration);
WebApkUkmRecorder.recordWebApkSessionDuration(
info.manifestUrl(), info.distributor(), info.webApkVersionCode(), sessionDuration);
super.onPauseWithNative();
}
......
......@@ -138,6 +138,7 @@ public class WebApkInfo extends WebappInfo {
public static final String RESOURCE_SHORT_NAME = "short_name";
public static final String RESOURCE_STRING_TYPE = "string";
// This enum is used to back UMA/UKM histograms, and should therefore be treated as append-only.
@IntDef({WebApkDistributor.BROWSER, WebApkDistributor.DEVICE_POLICY, WebApkDistributor.OTHER})
@Retention(RetentionPolicy.SOURCE)
public @interface WebApkDistributor {
......@@ -157,6 +158,7 @@ public class WebApkInfo extends WebappInfo {
private @WebApkDistributor int mDistributor;
private ShareTarget mShareTarget;
private String mShareTargetActivityName;
private int mApkVersionCode;
private Map<String, String> mIconUrlToMurmur2HashMap;
private boolean mIsSplashProvidedByWebApk;
......@@ -282,9 +284,12 @@ public class WebApkInfo extends WebappInfo {
}
Context appContext = ContextUtils.getApplicationContext();
PackageManager pm = appContext.getPackageManager();
Resources res = null;
int apkVersion = 0;
try {
res = appContext.getPackageManager().getResourcesForApplication(webApkPackageName);
res = pm.getResourcesForApplication(webApkPackageName);
apkVersion = pm.getPackageInfo(webApkPackageName, 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
......@@ -360,7 +365,8 @@ public class WebApkInfo extends WebappInfo {
displayMode, orientation, source, themeColor, backgroundColor,
defaultBackgroundColor, isPrimaryIconMaskable, webApkPackageName, shellApkVersion,
manifestUrl, manifestStartUrl, distributor, iconUrlToMurmur2HashMap, shareTarget,
shareTargetActivityName, forceNavigation, isSplashProvidedByWebApk, shareData);
shareTargetActivityName, forceNavigation, isSplashProvidedByWebApk, shareData,
apkVersion);
}
/**
......@@ -399,6 +405,7 @@ public class WebApkInfo extends WebappInfo {
* display the splash screen and (2) has a content provider
* which provides a screenshot of the splash screen.
* @param shareData Shared information from the share intent.
* @param webApkVersionCode WebAPK's version code.
*/
public static WebApkInfo create(String id, String url, String scope, Icon primaryIcon,
Icon badgeIcon, Icon splashIcon, String name, String shortName,
......@@ -408,7 +415,7 @@ public class WebApkInfo extends WebappInfo {
String manifestStartUrl, @WebApkDistributor int distributor,
Map<String, String> iconUrlToMurmur2HashMap, ShareTarget shareTarget,
String shareTargetActivityName, boolean forceNavigation,
boolean isSplashProvidedByWebApk, ShareData shareData) {
boolean isSplashProvidedByWebApk, ShareData shareData, int webApkVersionCode) {
if (id == null || url == null || manifestStartUrl == null || webApkPackageName == null) {
Log.e(TAG,
"Incomplete data provided: " + id + ", " + url + ", " + manifestStartUrl + ", "
......@@ -427,7 +434,8 @@ public class WebApkInfo extends WebappInfo {
displayMode, orientation, source, themeColor, backgroundColor,
defaultBackgroundColor, isPrimaryIconMaskable, webApkPackageName, shellApkVersion,
manifestUrl, manifestStartUrl, distributor, iconUrlToMurmur2HashMap, shareTarget,
shareTargetActivityName, forceNavigation, isSplashProvidedByWebApk, shareData);
shareTargetActivityName, forceNavigation, isSplashProvidedByWebApk, shareData,
webApkVersionCode);
}
protected WebApkInfo(String id, String url, String scope, Icon primaryIcon, Icon badgeIcon,
......@@ -437,7 +445,7 @@ public class WebApkInfo extends WebappInfo {
int shellApkVersion, String manifestUrl, String manifestStartUrl,
@WebApkDistributor int distributor, Map<String, String> iconUrlToMurmur2HashMap,
ShareTarget shareTarget, String shareTargetActivityName, boolean forceNavigation,
boolean isSplashProvidedByWebApk, ShareData shareData) {
boolean isSplashProvidedByWebApk, ShareData shareData, int webApkVersionCode) {
super(id, url, scope, primaryIcon, name, shortName, displayMode, orientation, source,
themeColor, backgroundColor, defaultBackgroundColor, false /* isIconGenerated */,
isPrimaryIconMaskable /* isIconAdaptive */, forceNavigation);
......@@ -456,6 +464,7 @@ public class WebApkInfo extends WebappInfo {
mShareTarget = new ShareTarget();
}
mShareTargetActivityName = shareTargetActivityName;
mApkVersionCode = webApkVersionCode;
}
protected WebApkInfo() {}
......@@ -486,6 +495,13 @@ public class WebApkInfo extends WebappInfo {
return mShareTargetActivityName;
}
/**
* Returns the WebAPK's version code.
*/
public int webApkVersionCode() {
return mApkVersionCode;
}
@Override
public boolean isForWebApk() {
return true;
......
// Copyright 2019 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.webapps;
/**
* A class to record User Keyed Metrics relevant to WebAPKs. This
* will allow us to concentrate on the use cases for the most used WebAPKs.
*/
public class WebApkUkmRecorder {
/**
* Records the duration, in exponentially-bucketed milliseconds, of a WebAPK session (from
* launch/foreground to background).
*/
public static void recordWebApkSessionDuration(String manifestUrl,
@WebApkInfo.WebApkDistributor int distributor, int versionCode, long duration) {
nativeRecordSessionDuration(manifestUrl, distributor, versionCode, duration);
}
private static native void nativeRecordSessionDuration(
String manifestUrl, int distributor, int versionCode, long duration);
}
......@@ -134,7 +134,7 @@ public class WebApkUpdateDataFetcher extends EmptyTabObserver {
mOldInfo.webApkPackageName(), mOldInfo.shellApkVersion(), mOldInfo.manifestUrl(),
manifestStartUrl, WebApkInfo.WebApkDistributor.BROWSER, iconUrlToMurmur2HashMap,
shareTarget, null, mOldInfo.shouldForceNavigation(),
mOldInfo.isSplashProvidedByWebApk(), null);
mOldInfo.isSplashProvidedByWebApk(), null, mOldInfo.webApkVersionCode());
mObserver.onGotManifestData(info, primaryIconUrl, badgeIconUrl);
}
......
......@@ -6,8 +6,6 @@ package org.chromium.chrome.browser.webapps;
import static org.chromium.webapk.lib.common.WebApkConstants.WEBAPK_PACKAGE_PREFIX;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Handler;
import android.text.TextUtils;
......@@ -231,21 +229,6 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
}
}
/**
* Reads the WebAPK's version code. Returns 0 on failure.
*/
private int readVersionCodeFromAndroidManifest(String webApkPackage) {
try {
PackageManager packageManager =
ContextUtils.getApplicationContext().getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(webApkPackage, 0);
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
/**
* Whether there is a new version of the //chrome/android/webapk/shell_apk code.
*/
......@@ -377,7 +360,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
protected void storeWebApkUpdateRequestToFile(String updateRequestPath, WebApkInfo info,
String primaryIconUrl, String badgeIconUrl, boolean isManifestStale,
@WebApkUpdateReason int updateReason, Callback<Boolean> callback) {
int versionCode = readVersionCodeFromAndroidManifest(info.webApkPackageName());
int versionCode = info.webApkVersionCode();
int size = info.iconUrlToMurmur2HashMap().size();
String[] iconUrls = new String[size];
String[] iconHashes = new String[size];
......
......@@ -106,7 +106,8 @@ public class WebApkUpdateDataFetcherTest {
-1, -1, -1, -1, -1, -1, false, "random.package", -1, manifestUrl, "",
WebApkInfo.WebApkDistributor.BROWSER, new HashMap<String, String>(), null,
null /*shareTargetActivityName*/, false /* forceNavigation */,
false /* isSplashProvidedByWebApk */, null /* shareData */);
false /* isSplashProvidedByWebApk */, null /* shareData */,
1 /* webApkVersionCode */);
fetcher.start(mTab, oldInfo, observer);
});
}
......
......@@ -159,7 +159,7 @@ public class WebApkUpdateManagerTest {
creationData.startUrl, WebApkInfo.WebApkDistributor.BROWSER,
creationData.iconUrlToMurmur2HashMap, null, null /*shareTargetActivityName*/,
false /* forceNavigation */, false /* isSplashProvidedByWebApk */,
null /* shareData */
null /* shareData */, 1 /* webApkVersionCode */
);
updateManager.updateIfNeeded(mTab, info);
......
......@@ -136,7 +136,8 @@ public class WebappVisibilityTest {
"", "", webappStartUrlOrScopeUrl, null, null, null, null, null, displayMode,
0, 0, 0, 0, 0, false, "", 0, null, "", WebApkInfo.WebApkDistributor.BROWSER,
null, null, null /*shareTargetActivityName*/, false /* forceNavigation */,
false /* isSplashProvidedByWebApk */, null /* shareData */
false /* isSplashProvidedByWebApk */, null /* shareData */,
1 /* webApkVersionCode */
);
}
......
......@@ -375,7 +375,8 @@ public class WebApkUpdateManagerUnitTest {
SHARE_TARGET_ENC_TYPE_MULTIPART),
manifestData.shareTargetFileNames, manifestData.shareTargetFileAccepts),
null /* shareTargetActivityName */, false /* forceNavigation */,
false /* isSplashProvidedByWebApk */, null /* shareData */);
false /* isSplashProvidedByWebApk */, null /* shareData */,
1 /* webApkVersionCode */);
}
/**
......
......@@ -2669,6 +2669,8 @@ jumbo_split_static_library("browser") {
"android/webapps/add_to_homescreen_manager.h",
"android/webapps/single_tab_mode_tab_helper.cc",
"android/webapps/single_tab_mode_tab_helper.h",
"android/webapps/webapk_ukm_recorder.cc",
"android/webapps/webapk_ukm_recorder.h",
"android/webapps/webapp_registry.cc",
"android/webapps/webapp_registry.h",
"android/widget/thumbnail_generator.cc",
......
// Copyright 2019 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 "chrome/browser/android/webapps/webapk_ukm_recorder.h"
#include <jni.h>
#include "base/android/jni_string.h"
#include "chrome/android/chrome_jni_headers/WebApkUkmRecorder_jni.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "url/gurl.h"
using base::android::JavaParamRef;
// static
void WebApkUkmRecorder::RecordSessionDuration(const GURL& manifest_url,
int64_t distributor,
int64_t version_code,
int64_t duration) {
if (!manifest_url.is_valid())
return;
ukm::SourceId source_id = ukm::UkmRecorder::GetNewSourceID();
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
ukm_recorder->UpdateSourceURL(source_id, manifest_url);
ukm::builders::WebAPK_SessionEnd(source_id)
.SetDistributor(distributor)
.SetAppVersion(version_code)
.SetSessionDuration(ukm::GetExponentialBucketMinForUserTiming(duration))
.Record(ukm_recorder);
}
// Called by the Java counterpart to record the Session Duration UKM metric.
void JNI_WebApkUkmRecorder_RecordSessionDuration(
JNIEnv* env,
const JavaParamRef<jstring>& manifest_url,
jint distributor,
jint version_code,
jlong duration) {
WebApkUkmRecorder::RecordSessionDuration(
GURL(base::android::ConvertJavaStringToUTF8(env, manifest_url)),
distributor, version_code, duration);
}
// Copyright 2019 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.
#ifndef CHROME_BROWSER_ANDROID_WEBAPPS_WEBAPK_UKM_RECORDER_H_
#define CHROME_BROWSER_ANDROID_WEBAPPS_WEBAPK_UKM_RECORDER_H_
#include <stdint.h>
#include "base/macros.h"
class GURL;
// WebApkUkmRecorder is the C++ counterpart of
// org.chromium.chrome.browser.webapps's WebApkUkmRecorder in Java.
// It contains static WebAPK UKM metrics-recording logic, and only
// needs to be in a class so that it can be a friend of ukm::UkmRecorder.
// All of the actual JNI goes through raw functions in webapk_ukm_recorder.cc to
// avoid having to instantiate this class and deal with object lifetimes.
class WebApkUkmRecorder {
public:
static void RecordSessionDuration(const GURL& manifest_url,
int64_t distributor,
int64_t version_code,
int64_t duration);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(WebApkUkmRecorder);
};
#endif // CHROME_BROWSER_ANDROID_WEBAPPS_WEBAPK_UKM_RECORDER_H_
......@@ -18,6 +18,7 @@
#include "url/gurl.h"
class PermissionUmaUtil;
class WebApkUkmRecorder;
namespace blink {
class Document;
......@@ -75,6 +76,13 @@ class METRICS_EXPORT UkmRecorder {
friend metrics::UkmRecorderInterface;
friend PermissionUmaUtil;
// WebApkUkmRecorder records metrics about installed Webapps. Instead of using
// the current main frame URL, we want to record the URL of the Webapp
// manifest which identifies the current app. Therefore, WebApkUkmRecorder
// needs to be a friend so that it can access the private UpdateSourceURL()
// method.
friend WebApkUkmRecorder;
// Associates the SourceId with a URL. Most UKM recording code should prefer
// to use a shared SourceId that is already associated with a URL, rather
// than using this API directly. New uses of this API must be audited to
......
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