Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko.health; |
michael@0 | 7 | |
michael@0 | 8 | import android.content.ContentProviderClient; |
michael@0 | 9 | import android.content.Context; |
michael@0 | 10 | import android.util.Log; |
michael@0 | 11 | |
michael@0 | 12 | import org.mozilla.gecko.GeckoAppShell; |
michael@0 | 13 | import org.mozilla.gecko.GeckoEvent; |
michael@0 | 14 | import org.mozilla.gecko.GeckoProfile; |
michael@0 | 15 | |
michael@0 | 16 | import org.mozilla.gecko.background.healthreport.EnvironmentBuilder; |
michael@0 | 17 | import org.mozilla.gecko.background.common.GlobalConstants; |
michael@0 | 18 | import org.mozilla.gecko.background.healthreport.HealthReportConstants; |
michael@0 | 19 | import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage; |
michael@0 | 20 | import org.mozilla.gecko.background.healthreport.HealthReportGenerator; |
michael@0 | 21 | |
michael@0 | 22 | import org.mozilla.gecko.util.GeckoEventListener; |
michael@0 | 23 | import org.mozilla.gecko.util.ThreadUtils; |
michael@0 | 24 | |
michael@0 | 25 | import org.json.JSONException; |
michael@0 | 26 | import org.json.JSONObject; |
michael@0 | 27 | |
michael@0 | 28 | /** |
michael@0 | 29 | * BrowserHealthReporter is the browser's interface to the Firefox Health |
michael@0 | 30 | * Report report generator. |
michael@0 | 31 | * |
michael@0 | 32 | * Each instance registers Gecko event listeners, so keep a single instance |
michael@0 | 33 | * around for the life of the browser. Java callers should use this globally |
michael@0 | 34 | * available singleton. |
michael@0 | 35 | */ |
michael@0 | 36 | public class BrowserHealthReporter implements GeckoEventListener { |
michael@0 | 37 | private static final String LOGTAG = "GeckoHealthRep"; |
michael@0 | 38 | |
michael@0 | 39 | public static final String EVENT_REQUEST = "HealthReport:Request"; |
michael@0 | 40 | public static final String EVENT_RESPONSE = "HealthReport:Response"; |
michael@0 | 41 | |
michael@0 | 42 | protected final Context context; |
michael@0 | 43 | |
michael@0 | 44 | public BrowserHealthReporter() { |
michael@0 | 45 | GeckoAppShell.registerEventListener(EVENT_REQUEST, this); |
michael@0 | 46 | |
michael@0 | 47 | context = GeckoAppShell.getContext(); |
michael@0 | 48 | if (context == null) { |
michael@0 | 49 | throw new IllegalStateException("Null Gecko context"); |
michael@0 | 50 | } |
michael@0 | 51 | } |
michael@0 | 52 | |
michael@0 | 53 | public void uninit() { |
michael@0 | 54 | GeckoAppShell.unregisterEventListener(EVENT_REQUEST, this); |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | /** |
michael@0 | 58 | * Generate a new Health Report. |
michael@0 | 59 | * |
michael@0 | 60 | * This method performs IO, so call it from a background thread. |
michael@0 | 61 | * |
michael@0 | 62 | * @param since timestamp of first day to report (milliseconds since epoch). |
michael@0 | 63 | * @param lastPingTime timestamp when last health report was uploaded |
michael@0 | 64 | * (milliseconds since epoch). |
michael@0 | 65 | * @param profilePath path of the profile to generate report for. |
michael@0 | 66 | * @throws JSONException if JSON generation fails. |
michael@0 | 67 | * @throws IllegalStateException if the environment does not allow to generate a report. |
michael@0 | 68 | * @return non-null report. |
michael@0 | 69 | */ |
michael@0 | 70 | public JSONObject generateReport(long since, long lastPingTime, String profilePath) throws JSONException { |
michael@0 | 71 | // We abuse the life-cycle of an Android ContentProvider slightly by holding |
michael@0 | 72 | // onto a ContentProviderClient while we generate a payload. This keeps |
michael@0 | 73 | // our database storage alive, while also allowing us to share a database |
michael@0 | 74 | // connection with BrowserHealthRecorder and the uploader. |
michael@0 | 75 | // The ContentProvider owns all underlying Storage instances, so we don't |
michael@0 | 76 | // need to explicitly close them. |
michael@0 | 77 | ContentProviderClient client = EnvironmentBuilder.getContentProviderClient(context); |
michael@0 | 78 | if (client == null) { |
michael@0 | 79 | throw new IllegalStateException("Could not fetch Health Report content provider."); |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | try { |
michael@0 | 83 | // Storage instance is owned by HealthReportProvider, so we don't need |
michael@0 | 84 | // to close it. |
michael@0 | 85 | HealthReportDatabaseStorage storage = EnvironmentBuilder.getStorage(client, profilePath); |
michael@0 | 86 | if (storage == null) { |
michael@0 | 87 | throw new IllegalStateException("No storage in Health Reporter."); |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | HealthReportGenerator generator = new HealthReportGenerator(storage); |
michael@0 | 91 | JSONObject report = generator.generateDocument(since, lastPingTime, profilePath); |
michael@0 | 92 | if (report == null) { |
michael@0 | 93 | throw new IllegalStateException("Not enough profile information to generate report."); |
michael@0 | 94 | } |
michael@0 | 95 | return report; |
michael@0 | 96 | } finally { |
michael@0 | 97 | client.release(); |
michael@0 | 98 | } |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | /** |
michael@0 | 102 | * Get last time a health report was successfully uploaded. |
michael@0 | 103 | * |
michael@0 | 104 | * This is read from shared preferences, so call it from a background |
michael@0 | 105 | * thread. Bug 882182 tracks making this work with multiple profiles. |
michael@0 | 106 | * |
michael@0 | 107 | * @return milliseconds since the epoch, or 0 if never uploaded. |
michael@0 | 108 | */ |
michael@0 | 109 | protected long getLastUploadLocalTime() { |
michael@0 | 110 | return context |
michael@0 | 111 | .getSharedPreferences(HealthReportConstants.PREFS_BRANCH, 0) |
michael@0 | 112 | .getLong(HealthReportConstants.PREF_LAST_UPLOAD_LOCAL_TIME, 0L); |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | /** |
michael@0 | 116 | * Generate a new Health Report for the current Gecko profile. |
michael@0 | 117 | * |
michael@0 | 118 | * This method performs IO, so call it from a background thread. |
michael@0 | 119 | * |
michael@0 | 120 | * @throws JSONException if JSON generation fails. |
michael@0 | 121 | * @throws IllegalStateException if the environment does not allow to generate a report. |
michael@0 | 122 | * @return non-null Health Report. |
michael@0 | 123 | */ |
michael@0 | 124 | public JSONObject generateReport() throws JSONException { |
michael@0 | 125 | GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile(); |
michael@0 | 126 | String profilePath = profile.getDir().getAbsolutePath(); |
michael@0 | 127 | |
michael@0 | 128 | long since = System.currentTimeMillis() - GlobalConstants.MILLISECONDS_PER_SIX_MONTHS; |
michael@0 | 129 | long lastPingTime = Math.max(getLastUploadLocalTime(), HealthReportConstants.EARLIEST_LAST_PING); |
michael@0 | 130 | |
michael@0 | 131 | return generateReport(since, lastPingTime, profilePath); |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | @Override |
michael@0 | 135 | public void handleMessage(String event, JSONObject message) { |
michael@0 | 136 | try { |
michael@0 | 137 | ThreadUtils.postToBackgroundThread(new Runnable() { |
michael@0 | 138 | @Override |
michael@0 | 139 | public void run() { |
michael@0 | 140 | JSONObject report = null; |
michael@0 | 141 | try { |
michael@0 | 142 | report = generateReport(); // non-null if it returns. |
michael@0 | 143 | } catch (Exception e) { |
michael@0 | 144 | Log.e(LOGTAG, "Generating report failed; responding with empty report.", e); |
michael@0 | 145 | report = new JSONObject(); |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(EVENT_RESPONSE, report.toString())); |
michael@0 | 149 | } |
michael@0 | 150 | }); |
michael@0 | 151 | } catch (Exception e) { |
michael@0 | 152 | Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); |
michael@0 | 153 | } |
michael@0 | 154 | } |
michael@0 | 155 | } |
michael@0 | 156 |