mobile/android/base/background/healthreport/HealthReportBroadcastService.java

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:dd662ea79412
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 package org.mozilla.gecko.background.healthreport;
6
7 import org.mozilla.gecko.background.BackgroundService;
8 import org.mozilla.gecko.background.common.GlobalConstants;
9 import org.mozilla.gecko.background.common.log.Logger;
10 import org.mozilla.gecko.background.healthreport.prune.HealthReportPruneService;
11 import org.mozilla.gecko.background.healthreport.upload.HealthReportUploadService;
12 import org.mozilla.gecko.background.healthreport.upload.ObsoleteDocumentTracker;
13
14 import android.app.AlarmManager;
15 import android.app.PendingIntent;
16 import android.content.Context;
17 import android.content.Intent;
18 import android.content.SharedPreferences;
19 import android.content.SharedPreferences.Editor;
20
21 /**
22 * A service which listens to broadcast intents from the system and from the
23 * browser, registering or unregistering the background health report services with the
24 * {@link AlarmManager}.
25 */
26 public class HealthReportBroadcastService extends BackgroundService {
27 public static final String LOG_TAG = HealthReportBroadcastService.class.getSimpleName();
28 public static final String WORKER_THREAD_NAME = LOG_TAG + "Worker";
29
30 public HealthReportBroadcastService() {
31 super(WORKER_THREAD_NAME);
32 }
33
34 protected SharedPreferences getSharedPreferences() {
35 return this.getSharedPreferences(HealthReportConstants.PREFS_BRANCH, GlobalConstants.SHARED_PREFERENCES_MODE);
36 }
37
38 public long getSubmissionPollInterval() {
39 return getSharedPreferences().getLong(HealthReportConstants.PREF_SUBMISSION_INTENT_INTERVAL_MSEC, HealthReportConstants.DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC);
40 }
41
42 public void setSubmissionPollInterval(final long interval) {
43 getSharedPreferences().edit().putLong(HealthReportConstants.PREF_SUBMISSION_INTENT_INTERVAL_MSEC, interval).commit();
44 }
45
46 public long getPrunePollInterval() {
47 return getSharedPreferences().getLong(HealthReportConstants.PREF_PRUNE_INTENT_INTERVAL_MSEC,
48 HealthReportConstants.DEFAULT_PRUNE_INTENT_INTERVAL_MSEC);
49 }
50
51 public void setPrunePollInterval(final long interval) {
52 getSharedPreferences().edit().putLong(HealthReportConstants.PREF_PRUNE_INTENT_INTERVAL_MSEC,
53 interval).commit();
54 }
55
56 /**
57 * Set or cancel an alarm to submit data for a profile.
58 *
59 * @param context
60 * Android context.
61 * @param profileName
62 * to submit data for.
63 * @param profilePath
64 * to submit data for.
65 * @param enabled
66 * whether the user has enabled submitting health report data for
67 * this profile.
68 * @param serviceEnabled
69 * whether submitting should be scheduled. If the user turns off
70 * submitting, <code>enabled</code> could be false but we could need
71 * to delete so <code>serviceEnabled</code> could be true.
72 */
73 protected void toggleSubmissionAlarm(final Context context, String profileName, String profilePath,
74 boolean enabled, boolean serviceEnabled) {
75 final Class<?> serviceClass = HealthReportUploadService.class;
76 Logger.info(LOG_TAG, (serviceEnabled ? "R" : "Unr") + "egistering " +
77 serviceClass.getSimpleName() + ".");
78
79 // PendingIntents are compared without reference to their extras. Therefore
80 // even though we pass the profile details to the action, different
81 // profiles will share the *same* pending intent. In a multi-profile future,
82 // this will need to be addressed. See Bug 882182.
83 final Intent service = new Intent(context, serviceClass);
84 service.setAction("upload"); // PendingIntents "lose" their extras if no action is set.
85 service.putExtra("uploadEnabled", enabled);
86 service.putExtra("profileName", profileName);
87 service.putExtra("profilePath", profilePath);
88 final PendingIntent pending = PendingIntent.getService(context, 0, service, PendingIntent.FLAG_CANCEL_CURRENT);
89
90 if (!serviceEnabled) {
91 cancelAlarm(pending);
92 return;
93 }
94
95 final long pollInterval = getSubmissionPollInterval();
96 scheduleAlarm(pollInterval, pending);
97 }
98
99 @Override
100 protected void onHandleIntent(Intent intent) {
101 Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
102
103 // Intent can be null. Bug 1025937.
104 if (intent == null) {
105 Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
106 return;
107 }
108
109 // The same intent can be handled by multiple methods so do not short-circuit evaluate.
110 boolean handled = attemptHandleIntentForUpload(intent);
111 handled = attemptHandleIntentForPrune(intent) ? true : handled;
112
113 if (!handled) {
114 Logger.warn(LOG_TAG, "Unhandled intent with action " + intent.getAction() + ".");
115 }
116 }
117
118 /**
119 * Attempts to handle the given intent for FHR document upload. If it cannot, false is returned.
120 *
121 * @param intent must be non-null.
122 */
123 private boolean attemptHandleIntentForUpload(final Intent intent) {
124 if (HealthReportConstants.UPLOAD_FEATURE_DISABLED) {
125 Logger.debug(LOG_TAG, "Health report upload feature is compile-time disabled; not handling intent.");
126 return false;
127 }
128
129 final String action = intent.getAction();
130 Logger.debug(LOG_TAG, "Health report upload feature is compile-time enabled; attempting to " +
131 "handle intent with action " + action + ".");
132
133 if (HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF.equals(action)) {
134 handleUploadPrefIntent(intent);
135 return true;
136 }
137
138 if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
139 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
140 BackgroundService.reflectContextToFennec(this,
141 GlobalConstants.GECKO_PREFERENCES_CLASS,
142 GlobalConstants.GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD);
143 return true;
144 }
145
146 return false;
147 }
148
149 /**
150 * Handle the intent sent by the browser when it wishes to notify us
151 * of the value of the user preference. Look at the value and toggle the
152 * alarm service accordingly.
153 *
154 * @param intent must be non-null.
155 */
156 private void handleUploadPrefIntent(Intent intent) {
157 if (!intent.hasExtra("enabled")) {
158 Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without enabled. Ignoring.");
159 return;
160 }
161
162 final boolean enabled = intent.getBooleanExtra("enabled", true);
163 Logger.debug(LOG_TAG, intent.getStringExtra("branch") + "/" +
164 intent.getStringExtra("pref") + " = " +
165 (intent.hasExtra("enabled") ? enabled : ""));
166
167 String profileName = intent.getStringExtra("profileName");
168 String profilePath = intent.getStringExtra("profilePath");
169
170 if (profileName == null || profilePath == null) {
171 Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without profilePath or profileName. Ignoring.");
172 return;
173 }
174
175 Logger.pii(LOG_TAG, "Updating health report upload alarm for profile " + profileName + " at " +
176 profilePath + ".");
177
178 final SharedPreferences sharedPrefs = getSharedPreferences();
179 final ObsoleteDocumentTracker tracker = new ObsoleteDocumentTracker(sharedPrefs);
180 final boolean hasObsoleteIds = tracker.hasObsoleteIds();
181
182 if (!enabled) {
183 final Editor editor = sharedPrefs.edit();
184 editor.remove(HealthReportConstants.PREF_LAST_UPLOAD_DOCUMENT_ID);
185
186 if (hasObsoleteIds) {
187 Logger.debug(LOG_TAG, "Health report upload disabled; scheduling deletion of " + tracker.numberOfObsoleteIds() + " documents.");
188 tracker.limitObsoleteIds();
189 } else {
190 // Primarily intended for debugging and testing.
191 Logger.debug(LOG_TAG, "Health report upload disabled and no deletes to schedule: clearing prefs.");
192 editor.remove(HealthReportConstants.PREF_FIRST_RUN);
193 editor.remove(HealthReportConstants.PREF_NEXT_SUBMISSION);
194 }
195
196 editor.commit();
197 }
198
199 // The user can toggle us off or on, or we can have obsolete documents to
200 // remove.
201 final boolean serviceEnabled = hasObsoleteIds || enabled;
202 toggleSubmissionAlarm(this, profileName, profilePath, enabled, serviceEnabled);
203 }
204
205 /**
206 * Attempts to handle the given intent for FHR data pruning. If it cannot, false is returned.
207 *
208 * @param intent must be non-null.
209 */
210 private boolean attemptHandleIntentForPrune(final Intent intent) {
211 final String action = intent.getAction();
212 Logger.debug(LOG_TAG, "Prune: Attempting to handle intent with action, " + action + ".");
213
214 if (HealthReportConstants.ACTION_HEALTHREPORT_PRUNE.equals(action)) {
215 handlePruneIntent(intent);
216 return true;
217 }
218
219 if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
220 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
221 BackgroundService.reflectContextToFennec(this,
222 GlobalConstants.GECKO_PREFERENCES_CLASS,
223 GlobalConstants.GECKO_BROADCAST_HEALTHREPORT_PRUNE_METHOD);
224 return true;
225 }
226
227 return false;
228 }
229
230 /**
231 * @param intent must be non-null.
232 */
233 private void handlePruneIntent(final Intent intent) {
234 final String profileName = intent.getStringExtra("profileName");
235 final String profilePath = intent.getStringExtra("profilePath");
236
237 if (profileName == null || profilePath == null) {
238 Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_PRUNE + " intent " +
239 "without profilePath or profileName. Ignoring.");
240 return;
241 }
242
243 final Class<?> serviceClass = HealthReportPruneService.class;
244 final Intent service = new Intent(this, serviceClass);
245 service.setAction("prune"); // Intents without actions have their extras removed.
246 service.putExtra("profileName", profileName);
247 service.putExtra("profilePath", profilePath);
248 final PendingIntent pending = PendingIntent.getService(this, 0, service,
249 PendingIntent.FLAG_CANCEL_CURRENT);
250
251 // Set a regular alarm to start PruneService. Since the various actions that PruneService can
252 // take occur on irregular intervals, we can be more efficient by only starting the Service
253 // when one of these time limits runs out. However, subsequent Service invocations must then
254 // be registered by the PruneService itself, which would fail if the PruneService crashes.
255 // Thus, we set this regular (and slightly inefficient) alarm.
256 Logger.info(LOG_TAG, "Registering " + serviceClass.getSimpleName() + ".");
257 final long pollInterval = getPrunePollInterval();
258 scheduleAlarm(pollInterval, pending);
259 }
260 }

mercurial