Bug 1329157 - Safely collect caller app information. r?sebastian,frank data-r?bsmedberg
MozReview-Commit-ID: 7oXYArRyWKY
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -10,16 +10,17 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.Browser;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.StyleRes;
import android.support.annotation.VisibleForTesting;
import android.support.design.widget.Snackbar;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.MenuItemCompat;
@@ -30,28 +31,31 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageButton;
import android.widget.ProgressBar;
+import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SnackbarBuilder;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuInflater;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.ColorUtil;
+import org.mozilla.gecko.util.IntentUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu;
+import org.mozilla.gecko.util.GeckoBundle;
import java.util.List;
import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
private static final String LOGTAG = "CustomTabsActivity";
private static final String SAVED_TOOLBAR_COLOR = "SavedToolbarColor";
@@ -73,16 +77,18 @@ public class CustomTabsActivity extends
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
toolbarColor = savedInstanceState.getInt(SAVED_TOOLBAR_COLOR, DEFAULT_ACTION_BAR_COLOR);
} else {
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
toolbarColor = getIntent().getIntExtra(EXTRA_TOOLBAR_COLOR, DEFAULT_ACTION_BAR_COLOR);
+ final String host = getReferrerHost();
+ recordCustomTabUsage(host);
}
// Translucent color does not make sense for toolbar color. Ensure it is 0xFF.
toolbarColor = 0xFF000000 | toolbarColor;
setThemeFromToolbarColor();
mProgressView = (ProgressBar) findViewById(R.id.page_progress);
@@ -95,16 +101,27 @@ public class CustomTabsActivity extends
actionBarPresenter.displayUrlOnly(getIntent().getDataString());
actionBarPresenter.setBackgroundColor(toolbarColor, getWindow());
actionBarPresenter.setTextLongClickListener(new UrlCopyListener());
actionBar.setDisplayHomeAsUpEnabled(true);
Tabs.registerOnTabsChangedListener(this);
}
+ private void recordCustomTabUsage(final String host) {
+ final GeckoBundle data = new GeckoBundle(1);
+ if (host != null) {
+ data.putString("client", host);
+ } else {
+ data.putString("client", "unknown");
+ }
+ // Pass a message to Gecko to send Telemetry data
+ EventDispatcher.getInstance().dispatch("Telemetry:CustomTabsPing", data);
+ }
+
private void setThemeFromToolbarColor() {
@StyleRes
int styleRes = (ColorUtil.getReadableTextColor(toolbarColor) == Color.BLACK)
? R.style.GeckoCustomTabs_Light
: R.style.GeckoCustomTabs;
setTheme(styleRes);
}
@@ -435,16 +452,17 @@ public class CustomTabsActivity extends
}
}
private void onActionButtonClicked() {
PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(getIntent());
performPendingIntent(pendingIntent);
}
+
/**
* Callback for Share menu item.
*/
private void onShareClicked() {
final String url = Tabs.getInstance().getSelectedTab().getURL();
if (!TextUtils.isEmpty(url)) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
@@ -469,9 +487,26 @@ public class CustomTabsActivity extends
SnackbarBuilder.builder(CustomTabsActivity.this)
.message(R.string.custom_tabs_hint_url_copy)
.duration(Snackbar.LENGTH_SHORT)
.buildAndShow();
}
return true;
}
}
+
+ private String getReferrerHost() {
+ final Intent intent = this.getIntent();
+ String applicationId = IntentUtils.getStringExtraSafe(intent, Browser.EXTRA_APPLICATION_ID);
+ if (applicationId != null) {
+ return applicationId;
+ }
+ Uri referrer = intent.getParcelableExtra("android.intent.extra.REFERRER");
+ if (referrer != null) {
+ return referrer.getHost();
+ }
+ String referrerName = intent.getStringExtra("android.intent.extra.REFERRER_NAME");
+ if (referrerName != null) {
+ return Uri.parse(referrerName).getHost();
+ }
+ return null;
+ }
}
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -11,16 +11,17 @@ var Cr = Components.results;
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/AsyncPrefs.jsm");
Cu.import("resource://gre/modules/DelayedInit.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
if (AppConstants.ACCESSIBILITY) {
XPCOMUtils.defineLazyModuleGetter(this, "AccessFu",
"resource://gre/modules/accessibility/AccessFu.jsm");
}
XPCOMUtils.defineLazyModuleGetter(this, "Manifests",
"resource://gre/modules/Manifest.jsm");
@@ -408,16 +409,17 @@ var BrowserApp = {
"SaveAs:PDF",
"ScrollTo:FocusedInput",
"Session:Back",
"Session:Forward",
"Session:GetHistory",
"Session:Navigate",
"Session:Reload",
"Session:Stop",
+ "Telemetry:CustomTabsPing",
]);
// Provide compatibility for add-ons like QuitNow that send "Browser:Quit"
// as an observer notification.
Services.obs.addObserver((subject, topic, data) =>
this.quit(data ? JSON.parse(data) : undefined), "Browser:Quit", false);
Services.obs.addObserver(this, "android-get-pref", false);
@@ -1758,16 +1760,21 @@ var BrowserApp = {
case "ScrollTo:FocusedInput": {
// these messages come from a change in the viewable area and not user interaction
// we allow scrolling to the selected input, but not zooming the page
this.scrollToFocusedInput(browser, false);
break;
}
+ case "Telemetry:CustomTabsPing": {
+ TelemetryController.submitExternalPing("anonymous", { client: data.client }, { addClientId: false });
+ break;
+ }
+
case "Session:GetHistory": {
callback.onSuccess(this.getHistory(data));
break;
}
case "Session:Back":
browser.goBack();
break;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/data/anonymous-ping.rst
@@ -0,0 +1,64 @@
+
+"anonymous" ping
+============
+
+This ping is only for product survey purpose and will not track/associate client ID. It's used
+to evaluate custom tab usage and see which app is using our custom tab.
+
+Submission interval & triggers
+Since this ping is used to measure the feature usage, it should be sent each time the client app uses our custom tab.
+
+Dataset:
+Only opt-in users will send out this ping.
+Since all other pings will collect client ID. We need this custom ping to not do that.
+
+Size and volume:
+The size of submitted payload is small. And this custom ping should be deprecated after it's released for 6 months.
+
+Privacy:
+We won't collect customer information so there'll be no PI leak.
+
+Data contents:
+The content of this ping will let us know which app is using our custom tab.
+Just like other feature usage measurement, we only need it for opt-in users (which consider as heavy users).
+
+Structure:
+
+.. code-block:: js
+
+ {
+ "payload": {
+ "client": <string> // The package name of the caller app.
+ }
+ type: <string>, // "anonymous", "activation", "deletion", "saved-session", ...
+ id: <UUID>, // a UUID that identifies this ping
+ creationDate: <ISO date>, // the date the ping was generated
+ version: <number>, // the version of the ping format, currently 4
+
+ application: {
+ architecture: <string>, // build architecture, e.g. x86
+ buildId: <string>, // "20141126041045"
+ name: <string>, // "Firefox"
+ version: <string>, // "35.0"
+ displayVersion: <string>, // "35.0b3"
+ vendor: <string>, // "Mozilla"
+ platformVersion: <string>, // "35.0"
+ xpcomAbi: <string>, // e.g. "x86-msvc"
+ channel: <string>, // "beta"
+ },
+ }
+
+Field details
+-------------
+
+client
+~~~~~~
+It could be ``com.example.app``, which is the identifier of the app.
+
+Version history
+---------------
+* v1: initial version - Will be shipped in `Fennec 55 <https://bugzilla.mozilla.org/show_bug.cgi?id=1329157>`_.
+
+Notes
+~~~~~
+There's no option in this custom ping since we don't collect clientId nor environment data.
\ No newline at end of file
--- a/toolkit/components/telemetry/docs/data/index.rst
+++ b/toolkit/components/telemetry/docs/data/index.rst
@@ -7,11 +7,12 @@ Data documentation
:titlesonly:
:glob:
common-ping
environment
main-ping
deletion-ping
crash-ping
+ anonymous-ping
*-ping
The `mozilla-pipeline-schemas repository <https://github.com/mozilla-services/mozilla-pipeline-schemas/>`_ contains schemas for some of the pings.