Bug 1246816 - Get core ping profile creation date from application install time. r=sebastian a=ritu
authorMichael Comella <michael.l.comella@gmail.com>
Thu, 31 Mar 2016 15:30:39 -0700
changeset 324005 6eb56cc6d62fc03c8129db68a4ade7b3ebc2e012
parent 324004 940fa79188fc1cf919ff8b0f3a4bb306d378df25
child 324006 f96dd0b45c033d59e596f1ed1097ed3a71dc9eaf
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian, ritu
bugs1246816
milestone47.0a2
Bug 1246816 - Get core ping profile creation date from application install time. r=sebastian a=ritu MozReview-Commit-ID: Bo07XuqQDWl
mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPingGenerator.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
@@ -34,16 +34,17 @@ import org.mozilla.gecko.mozglue.Context
 import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
 import org.mozilla.gecko.preferences.DistroSharedPrefsImport;
 import org.mozilla.gecko.util.INIParser;
 import org.mozilla.gecko.util.INISection;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.SharedPreferences;
 import android.support.annotation.WorkerThread;
 import android.text.TextUtils;
 import android.util.Log;
 
 public final class GeckoProfile {
     private static final String LOGTAG = "GeckoProfile";
 
@@ -691,47 +692,73 @@ public final class GeckoProfile {
         // We could use UUID.fromString but, for consistency, we take the implementation from ClientID.jsm.
         if (TextUtils.isEmpty(clientId)) {
             return false;
         }
         return clientId.matches("(?i:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})");
     }
 
     /**
-     * @return the profile creation date in the format returned by {@link System#currentTimeMillis()} or -1 if the value
-     *         was not found.
+     * Gets the profile creation date and persists it if it had to be generated.
+     *
+     * To get this value, we first look in times.json. If that could not be accessed, we
+     * return the package's first install date. This is not a perfect solution because a
+     * user may have large gap between install time and first use.
+     *
+     * A more correct algorithm could be the one performed by the JS code in ProfileAge.jsm
+     * getOldestProfileTimestamp: walk the tree and return the oldest timestamp on the files
+     * within the profile. However, since times.json will only not exist for the small
+     * number of really old profiles, we're okay with the package install date compromise for
+     * simplicity.
+     *
+     * @return the profile creation date in the format returned by {@link System#currentTimeMillis()}
+     *         or -1 if the value could not be persisted.
      */
     @WorkerThread
-    public long getProfileCreationDate() {
+    public long getAndPersistProfileCreationDate(final Context context) {
         try {
             return getProfileCreationDateFromTimesFile();
         } catch (final IOException e) {
-            return getAndPersistProfileCreationDateFromFilesystem();
+            Log.d(LOGTAG, "Unable to retrieve profile creation date from times.json. Getting from system...");
+            final long packageInstallMillis = org.mozilla.gecko.util.ContextUtils.getPackageInstallTime(context);
+            try {
+                persistProfileCreationDateToTimesFile(packageInstallMillis);
+            } catch (final IOException ioEx) {
+                // We return -1 to ensure the profileCreationDate
+                // will either be an error (-1) or a consistent value.
+                Log.w(LOGTAG, "Unable to persist profile creation date - returning -1");
+                return -1;
+            }
+
+            return packageInstallMillis;
         }
     }
 
     @WorkerThread
     private long getProfileCreationDateFromTimesFile() throws IOException {
         final JSONObject obj = readJSONObjectFromFile(TIMES_PATH);
         try {
             return obj.getLong(PROFILE_CREATION_DATE_JSON_ATTR);
         } catch (final JSONException e) {
             // Don't log to avoid leaking data in JSONObject.
             throw new IOException("Profile creation does not exist in JSONObject");
         }
     }
 
-    /**
-     * TODO (bug 1246816): Implement ProfileAge.jsm - getOldestProfileTimestamp. Persist results to times.json.
-     * Update comment in getProfileCreationDate too.
-     * @return -1 until implemented.
-     */
     @WorkerThread
-    private long getAndPersistProfileCreationDateFromFilesystem() {
-        return -1;
+    private void persistProfileCreationDateToTimesFile(final long profileCreationMillis) throws IOException {
+        final JSONObject obj = new JSONObject();
+        try {
+            obj.put(PROFILE_CREATION_DATE_JSON_ATTR, profileCreationMillis);
+        } catch (final JSONException e) {
+            // Don't log to avoid leaking data in JSONObject.
+            throw new IOException("Unable to persist profile creation date to times file");
+        }
+        Log.d(LOGTAG, "Attempting to write new profile creation date");
+        writeFile(TIMES_PATH, obj.toString()); // Ideally we'd throw here too.
     }
 
     /**
      * Moves the session file to the backup session file.
      *
      * sessionstore.js should hold the current session, and sessionstore.bak
      * should hold the previous session (where it is used to read the "tabs
      * from last time"). Normally, sessionstore.js is moved to sessionstore.bak
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
@@ -24,17 +24,17 @@ public class TelemetryConstants {
 
     public static final String PREF_SERVER_URL = "telemetry-serverUrl";
     public static final String PREF_SEQ_COUNT = "telemetry-seqCount";
 
     public static class CorePing {
         private CorePing() { /* To prevent instantiation */ }
 
         public static final String NAME = "core";
-        public static final int VERSION_VALUE = 2; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
+        public static final int VERSION_VALUE = 3; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
         public static final String OS_VALUE = "Android";
 
         public static final String ARCHITECTURE = "arch";
         public static final String CLIENT_ID = "clientId";
         public static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
         public static final String DEVICE = "device";
         public static final String EXPERIMENTS = "experiments";
         public static final String LOCALE = "locale";
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPingGenerator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPingGenerator.java
@@ -87,17 +87,15 @@ public class TelemetryPingGenerator {
         ping.put(CorePing.ARCHITECTURE, AppConstants.ANDROID_CPU_ARCH);
         ping.put(CorePing.CLIENT_ID, clientId);
         ping.put(CorePing.DEFAULT_SEARCH_ENGINE, TextUtils.isEmpty(defaultSearchEngine) ? null : defaultSearchEngine);
         ping.put(CorePing.DEVICE, deviceDescriptor);
         ping.put(CorePing.LOCALE, Locales.getLanguageTag(Locale.getDefault()));
         ping.put(CorePing.OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
         ping.put(CorePing.SEQ, seq);
         ping.putArray(CorePing.EXPERIMENTS, Experiments.getActiveExperiments(context));
-        // TODO (bug 1246816): Remove this "optional" parameter work-around when
-        // GeckoProfile.getAndPersistProfileCreationDateFromFilesystem is implemented. That method returns -1
-        // while it's not implemented so we don't include the parameter in the ping if that's the case.
-        if (profileCreationDate >= 0) {
-            ping.put(CorePing.PROFILE_CREATION_DATE, profileCreationDate);
-        }
+
+        // `null` indicates failure more clearly than < 0.
+        final Long finalProfileCreationDate = (profileCreationDate < 0) ? null : profileCreationDate;
+        ping.put(CorePing.PROFILE_CREATION_DATE, finalProfileCreationDate);
         return ping;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
@@ -209,18 +209,18 @@ public class TelemetryUploadService exte
         resource.postBlocking(ping.getPayload());
     }
 
     /**
      * @return the profile creation date in the format expected by TelemetryPingGenerator.
      */
     @WorkerThread
     private long getProfileCreationDate(final GeckoProfile profile) {
-        final long profileMillis = profile.getProfileCreationDate();
-        // TODO (bug 1246816): Remove this work-around when finishing bug. getProfileCreationDate can return -1,
+        final long profileMillis = profile.getAndPersistProfileCreationDate(this);
+        // getAndPersistProfileCreationDate can return -1,
         // and we don't want to truncate (-1 / MILLIS) to 0.
         if (profileMillis < 0) {
             return profileMillis;
         }
         return (long) Math.floor((double) profileMillis / MILLIS_IN_DAY);
     }
 
     private static class CorePingResultDelegate extends ResultDelegate {