Bug 1329157 - Safely collect caller app information. r?sebastian,frank data-r?bsmedberg draft
authornechen <cnevinc@livemail.tw>
Tue, 14 Mar 2017 12:06:08 +0800
changeset 557279 29caecb9f41e3fb174e80fa18628df8e94da6994
parent 556295 720b9177c6856c1c4339d0fac1bf5149c0d53950
child 623006 2c8d72e37e7e8d0ca811d2fc08f2bcebebba2fd0
push id52669
push userbmo:cnevinchen@gmail.com
push dateThu, 06 Apr 2017 15:41:28 +0000
reviewerssebastian, frank
bugs1329157
milestone55.0a1
Bug 1329157 - Safely collect caller app information. r?sebastian,frank data-r?bsmedberg MozReview-Commit-ID: 7oXYArRyWKY
mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
mobile/android/chrome/content/browser.js
toolkit/components/telemetry/docs/data/anonymous-ping.rst
toolkit/components/telemetry/docs/data/index.rst
--- 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.