Commit aeb136f1 authored by mariakhomenko's avatar mariakhomenko Committed by Commit bot

Record how often we end up in Chrome from external navigations.

Also records the package names of the apps that cause external navigations
and package names for which the user chose Chrome to handle the URL over
the native application.

BUG=622906

Review-Url: https://codereview.chromium.org/2096073002
Cr-Commit-Position: refs/heads/master@{#403254}
parent cc96f13a
...@@ -23,10 +23,12 @@ import org.chromium.base.ApiCompatibilityUtils; ...@@ -23,10 +23,12 @@ import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils; import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl; import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
import org.chromium.chrome.browser.externalnav.IntentWithGesturesHandler; import org.chromium.chrome.browser.externalnav.IntentWithGesturesHandler;
import org.chromium.chrome.browser.omnibox.AutocompleteController; import org.chromium.chrome.browser.omnibox.AutocompleteController;
import org.chromium.chrome.browser.rappor.RapporServiceBridge;
import org.chromium.chrome.browser.search_engines.TemplateUrlService; import org.chromium.chrome.browser.search_engines.TemplateUrlService;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate; import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate;
...@@ -132,6 +134,12 @@ public class IntentHandler { ...@@ -132,6 +134,12 @@ public class IntentHandler {
*/ */
public static final String EXTRA_WINDOW_ID = "org.chromium.chrome.browser.window_id"; public static final String EXTRA_WINDOW_ID = "org.chromium.chrome.browser.window_id";
/**
* Records package names of other applications in the system that could have handled
* this intent.
*/
public static final String EXTRA_EXTERNAL_NAV_PACKAGES = "org.chromium.chrome.browser.eenp";
/** /**
* Fake ComponentName used in constructing TRUSTED_APPLICATION_CODE_EXTRA. * Fake ComponentName used in constructing TRUSTED_APPLICATION_CODE_EXTRA.
*/ */
...@@ -289,6 +297,22 @@ public class IntentHandler { ...@@ -289,6 +297,22 @@ public class IntentHandler {
externalId.ordinal(), ExternalAppId.INDEX_BOUNDARY.ordinal()); externalId.ordinal(), ExternalAppId.INDEX_BOUNDARY.ordinal());
} }
/**
* Records an action when a user chose to handle a URL in Chrome that could have been handled
* by an application installed on the phone. Also records the name of that application.
* This doesn't include generic URL handlers, such as browsers.
*/
private void recordAppHandlersForIntent(Intent intent) {
List<String> packages = IntentUtils.safeGetStringArrayListExtra(intent,
IntentHandler.EXTRA_EXTERNAL_NAV_PACKAGES);
if (packages != null && packages.size() > 0) {
RecordUserAction.record("MobileExternalNavigationReceived");
for (String name : packages) {
RapporServiceBridge.sampleString("Android.ExternalNavigationNotChosen", name);
}
}
}
/** /**
* Handles an Intent after the ChromeTabbedActivity decides that it shouldn't ignore the * Handles an Intent after the ChromeTabbedActivity decides that it shouldn't ignore the
* Intent. * Intent.
...@@ -316,6 +340,7 @@ public class IntentHandler { ...@@ -316,6 +340,7 @@ public class IntentHandler {
IntentUtils.safeGetStringExtra(intent, Browser.EXTRA_APPLICATION_ID), IntentUtils.safeGetStringExtra(intent, Browser.EXTRA_APPLICATION_ID),
tabIdToBringToFront, hasUserGesture, intent); tabIdToBringToFront, hasUserGesture, intent);
recordExternalIntentSourceUMA(intent); recordExternalIntentSourceUMA(intent);
recordAppHandlersForIntent(intent);
return true; return true;
} }
......
...@@ -32,7 +32,7 @@ interface ExternalNavigationDelegate { ...@@ -32,7 +32,7 @@ interface ExternalNavigationDelegate {
* Search for intent handlers that are specific to this URL aka, specialized apps like * Search for intent handlers that are specific to this URL aka, specialized apps like
* google maps or youtube * google maps or youtube
*/ */
boolean isSpecializedHandlerAvailable(List<ResolveInfo> intent); boolean isSpecializedHandlerAvailable(List<ResolveInfo> infos);
/** /**
* Returns the number of specialized intent handlers in {@params infos}. Specialized intent * Returns the number of specialized intent handlers in {@params infos}. Specialized intent
...@@ -106,6 +106,9 @@ interface ExternalNavigationDelegate { ...@@ -106,6 +106,9 @@ interface ExternalNavigationDelegate {
/** Adds a window id to the intent, if necessary. */ /** Adds a window id to the intent, if necessary. */
void maybeSetWindowId(Intent intent); void maybeSetWindowId(Intent intent);
/** Adds the package name of a specialized intent handler. */
void maybeRecordAppHandlersInIntent(Intent intent, List<ResolveInfo> info);
/** /**
* Determine if the Chrome app is in the foreground. * Determine if the Chrome app is in the foreground.
*/ */
......
...@@ -30,6 +30,8 @@ import org.chromium.base.ApplicationStatus; ...@@ -30,6 +30,8 @@ import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.PathUtils; import org.chromium.base.PathUtils;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.ChromeFeatureList;
...@@ -47,6 +49,7 @@ import org.chromium.ui.base.WindowAndroid; ...@@ -47,6 +49,7 @@ import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.WindowAndroid.PermissionCallback; import org.chromium.ui.base.WindowAndroid.PermissionCallback;
import org.chromium.webapk.lib.client.WebApkValidator; import org.chromium.webapk.lib.client.WebApkValidator;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
...@@ -239,13 +242,15 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -239,13 +242,15 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
@Override @Override
public int countSpecializedHandlers(List<ResolveInfo> infos) { public int countSpecializedHandlers(List<ResolveInfo> infos) {
return countSpecializedHandlersWithFilter(infos, null); return getSpecializedHandlersWithFilter(infos, null).size();
} }
static int countSpecializedHandlersWithFilter( @VisibleForTesting
static ArrayList<String> getSpecializedHandlersWithFilter(
List<ResolveInfo> infos, String filterPackageName) { List<ResolveInfo> infos, String filterPackageName) {
ArrayList<String> result = new ArrayList<String>();
if (infos == null) { if (infos == null) {
return 0; return result;
} }
int count = 0; int count = 0;
...@@ -266,9 +271,9 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -266,9 +271,9 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
continue; continue;
} }
++count; result.add(info.activityInfo != null ? info.activityInfo.packageName : "");
} }
return count; return result;
} }
/** /**
...@@ -285,7 +290,7 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -285,7 +290,7 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
try { try {
List<ResolveInfo> handlers = context.getPackageManager().queryIntentActivities( List<ResolveInfo> handlers = context.getPackageManager().queryIntentActivities(
intent, PackageManager.GET_RESOLVED_FILTER); intent, PackageManager.GET_RESOLVED_FILTER);
return countSpecializedHandlersWithFilter(handlers, packageName) > 0; return getSpecializedHandlersWithFilter(handlers, packageName).size() > 0;
} catch (RuntimeException e) { } catch (RuntimeException e) {
logTransactionTooLargeOrRethrow(e, intent); logTransactionTooLargeOrRethrow(e, intent);
} }
...@@ -312,6 +317,7 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -312,6 +317,7 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
Context context = getAvailableContext(); Context context = getAvailableContext();
if (!(context instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (!(context instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
recordExternalNavigationDispatched(intent);
} catch (RuntimeException e) { } catch (RuntimeException e) {
logTransactionTooLargeOrRethrow(e, intent); logTransactionTooLargeOrRethrow(e, intent);
} }
...@@ -319,6 +325,7 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -319,6 +325,7 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
@Override @Override
public boolean startActivityIfNeeded(Intent intent) { public boolean startActivityIfNeeded(Intent intent) {
boolean activityWasLaunched;
// Only touches disk on Kitkat. See http://crbug.com/617725 for more context. // Only touches disk on Kitkat. See http://crbug.com/617725 for more context.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
StrictMode.allowThreadDiskReads(); StrictMode.allowThreadDiskReads();
...@@ -326,10 +333,12 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -326,10 +333,12 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
forcePdfViewerAsIntentHandlerIfNeeded(mApplicationContext, intent); forcePdfViewerAsIntentHandlerIfNeeded(mApplicationContext, intent);
Context context = getAvailableContext(); Context context = getAvailableContext();
if (context instanceof Activity) { if (context instanceof Activity) {
return ((Activity) context).startActivityIfNeeded(intent, -1); activityWasLaunched = ((Activity) context).startActivityIfNeeded(intent, -1);
} else { } else {
return false; activityWasLaunched = false;
} }
if (activityWasLaunched) recordExternalNavigationDispatched(intent);
return activityWasLaunched;
} catch (RuntimeException e) { } catch (RuntimeException e) {
logTransactionTooLargeOrRethrow(e, intent); logTransactionTooLargeOrRethrow(e, intent);
return false; return false;
...@@ -338,6 +347,14 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -338,6 +347,14 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
} }
} }
private void recordExternalNavigationDispatched(Intent intent) {
ArrayList<String> specializedHandlers = intent.getStringArrayListExtra(
IntentHandler.EXTRA_EXTERNAL_NAV_PACKAGES);
if (specializedHandlers != null && specializedHandlers.size() > 0) {
RecordUserAction.record("MobileExternalNavigationDispatched");
}
}
@Override @Override
public void startIncognitoIntent(final Intent intent, final String referrerUrl, public void startIncognitoIntent(final Intent intent, final String referrerUrl,
final String fallbackUrl, final Tab tab, final boolean needsToCloseTab) { final String fallbackUrl, final Tab tab, final boolean needsToCloseTab) {
...@@ -522,4 +539,10 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat ...@@ -522,4 +539,10 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
return PDF_EXTENSION.equals(fileExtension); return PDF_EXTENSION.equals(fileExtension);
} }
@Override
public void maybeRecordAppHandlersInIntent(Intent intent, List<ResolveInfo> infos) {
intent.putExtra(IntentHandler.EXTRA_EXTERNAL_NAV_PACKAGES,
getSpecializedHandlersWithFilter(infos, null));
}
} }
...@@ -357,6 +357,7 @@ public class ExternalNavigationHandler { ...@@ -357,6 +357,7 @@ public class ExternalNavigationHandler {
if (params.isOpenInNewTab()) intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); if (params.isOpenInNewTab()) intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mDelegate.maybeSetWindowId(intent); mDelegate.maybeSetWindowId(intent);
mDelegate.maybeRecordAppHandlersInIntent(intent, resolvingInfos);
if (params.getReferrerUrl() != null) { if (params.getReferrerUrl() != null) {
IntentHandler.setPendingReferrer(intent, params.getReferrerUrl()); IntentHandler.setPendingReferrer(intent, params.getReferrerUrl());
......
...@@ -34,8 +34,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa ...@@ -34,8 +34,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa
public void testIsPackageSpecializedHandler_NoResolveInfo() { public void testIsPackageSpecializedHandler_NoResolveInfo() {
String packageName = ""; String packageName = "";
List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>(); List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();
assertEquals(0, ExternalNavigationDelegateImpl.countSpecializedHandlersWithFilter( assertEquals(0, ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
resolveInfos, packageName)); resolveInfos, packageName).size());
} }
@SmallTest @SmallTest
...@@ -44,8 +44,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa ...@@ -44,8 +44,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa
ResolveInfo info = new ResolveInfo(); ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter(); info.filter = new IntentFilter();
List<ResolveInfo> resolveInfos = makeResolveInfos(info); List<ResolveInfo> resolveInfos = makeResolveInfos(info);
assertEquals(0, ExternalNavigationDelegateImpl.countSpecializedHandlersWithFilter( assertEquals(0, ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
resolveInfos, packageName)); resolveInfos, packageName).size());
} }
@SmallTest @SmallTest
...@@ -55,8 +55,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa ...@@ -55,8 +55,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa
info.filter = new IntentFilter(); info.filter = new IntentFilter();
info.filter.addDataPath("somepath", 2); info.filter.addDataPath("somepath", 2);
List<ResolveInfo> resolveInfos = makeResolveInfos(info); List<ResolveInfo> resolveInfos = makeResolveInfos(info);
assertEquals(1, ExternalNavigationDelegateImpl.countSpecializedHandlersWithFilter( assertEquals(1, ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
resolveInfos, packageName)); resolveInfos, packageName).size());
} }
@SmallTest @SmallTest
...@@ -66,8 +66,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa ...@@ -66,8 +66,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa
info.filter = new IntentFilter(); info.filter = new IntentFilter();
info.filter.addDataAuthority("http://www.google.com", "80"); info.filter.addDataAuthority("http://www.google.com", "80");
List<ResolveInfo> resolveInfos = makeResolveInfos(info); List<ResolveInfo> resolveInfos = makeResolveInfos(info);
assertEquals(1, ExternalNavigationDelegateImpl.countSpecializedHandlersWithFilter( assertEquals(1, ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
resolveInfos, packageName)); resolveInfos, packageName).size());
} }
@SmallTest @SmallTest
...@@ -79,8 +79,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa ...@@ -79,8 +79,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa
info.activityInfo = new ActivityInfo(); info.activityInfo = new ActivityInfo();
info.activityInfo.packageName = packageName; info.activityInfo.packageName = packageName;
List<ResolveInfo> resolveInfos = makeResolveInfos(info); List<ResolveInfo> resolveInfos = makeResolveInfos(info);
assertEquals(1, ExternalNavigationDelegateImpl.countSpecializedHandlersWithFilter( assertEquals(1, ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
resolveInfos, packageName)); resolveInfos, packageName).size());
} }
@SmallTest @SmallTest
...@@ -92,8 +92,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa ...@@ -92,8 +92,8 @@ public class ExternalNavigationDelegateImplTest extends ChromeActivityTestCaseBa
info.activityInfo = new ActivityInfo(); info.activityInfo = new ActivityInfo();
info.activityInfo.packageName = "com.foo.bar"; info.activityInfo.packageName = "com.foo.bar";
List<ResolveInfo> resolveInfos = makeResolveInfos(info); List<ResolveInfo> resolveInfos = makeResolveInfos(info);
assertEquals(0, ExternalNavigationDelegateImpl.countSpecializedHandlersWithFilter( assertEquals(0, ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
resolveInfos, packageName)); resolveInfos, packageName).size());
} }
@SmallTest @SmallTest
......
...@@ -1001,9 +1001,14 @@ public class ExternalNavigationHandlerTest extends InstrumentationTestCase { ...@@ -1001,9 +1001,14 @@ public class ExternalNavigationHandlerTest extends InstrumentationTestCase {
} }
@Override @Override
public int countSpecializedHandlers(List<ResolveInfo> infos) { public int countSpecializedHandlers(List<ResolveInfo> resolveInfos) {
return getSpecializedHandlers(resolveInfos).size();
}
private ArrayList<String> getSpecializedHandlers(List<ResolveInfo> infos) {
ArrayList<String> result = new ArrayList<>();
if (infos == null) { if (infos == null) {
return 0; return result;
} }
int count = 0; int count = 0;
for (ResolveInfo info : infos) { for (ResolveInfo info : infos) {
...@@ -1013,10 +1018,10 @@ public class ExternalNavigationHandlerTest extends InstrumentationTestCase { ...@@ -1013,10 +1018,10 @@ public class ExternalNavigationHandlerTest extends InstrumentationTestCase {
|| packageName.equals(NATIVE_APP_PACKAGE_NAME) || packageName.equals(NATIVE_APP_PACKAGE_NAME)
|| packageName.equals(WEBAPK_PACKAGE_NAME) || packageName.equals(WEBAPK_PACKAGE_NAME)
|| packageName.equals(WEBAPK_WITH_NATIVE_APP_PACKAGE_NAME)) { || packageName.equals(WEBAPK_WITH_NATIVE_APP_PACKAGE_NAME)) {
++count; result.add(packageName);
} }
} }
return count; return result;
} }
@Override @Override
...@@ -1081,6 +1086,10 @@ public class ExternalNavigationHandlerTest extends InstrumentationTestCase { ...@@ -1081,6 +1086,10 @@ public class ExternalNavigationHandlerTest extends InstrumentationTestCase {
public void maybeSetWindowId(Intent intent) { public void maybeSetWindowId(Intent intent) {
} }
@Override
public void maybeRecordAppHandlersInIntent(Intent intent, List<ResolveInfo> info) {
}
@Override @Override
public boolean isChromeAppInForeground() { public boolean isChromeAppInForeground() {
return mIsChromeAppInForeground; return mIsChromeAppInForeground;
......
...@@ -8794,6 +8794,24 @@ should be able to be added at any place in this file. ...@@ -8794,6 +8794,24 @@ should be able to be added at any place in this file.
<obsolete>Deprecated as of 8/2015</obsolete> <obsolete>Deprecated as of 8/2015</obsolete>
</action> </action>
<action name="MobileExternalNavigationDispatched">
<owner>mariakhomenko@chromium.org</owner>
<description>
Records when the system started a VIEW intent on a navigation click. This
happens when there is another application in the system that's not a browser
and that can handle this URL (e.g. Maps app for http://maps.google.com/
url).
</description>
</action>
<action name="MobileExternalNavigationReceived">
<owner>mariakhomenko@chromium.org</owner>
<description>
Records when a user was given a choice between using Chrome and an installed
app and chose Chrome.
</description>
</action>
<action name="MobileFirstEditInOmnibox"> <action name="MobileFirstEditInOmnibox">
<owner>Please list the metric's owners. Add more owner tags as needed.</owner> <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
<description>Please enter the description of this user action.</description> <description>Please enter the description of this user action.</description>
......
...@@ -137,6 +137,15 @@ components/rappor/rappor_parameters.h. ...@@ -137,6 +137,15 @@ components/rappor/rappor_parameters.h.
<rappor-metrics> <rappor-metrics>
<!-- Rappor metric definitions --> <!-- Rappor metric definitions -->
<rappor-metric name="Android.ExternalNavigationNotChosen"
type="UMA_RAPPOR_TYPE">
<owner>mariakhomenko@chromium.org</owner>
<summary>
The package name of an application that was given as an alternative choice
to handling a URL in Chrome, but wasn't chosen.
</summary>
</rappor-metric>
<rappor-metric name="AppBanner.NativeApp.Dismissed" type="ETLD_PLUS_ONE"> <rappor-metric name="AppBanner.NativeApp.Dismissed" type="ETLD_PLUS_ONE">
<owner>dfalcantara@chromium.org</owner> <owner>dfalcantara@chromium.org</owner>
<summary> <summary>
......
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