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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/background/healthreport/HealthReportBroadcastService.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,260 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.background.healthreport;
     1.9 +
    1.10 +import org.mozilla.gecko.background.BackgroundService;
    1.11 +import org.mozilla.gecko.background.common.GlobalConstants;
    1.12 +import org.mozilla.gecko.background.common.log.Logger;
    1.13 +import org.mozilla.gecko.background.healthreport.prune.HealthReportPruneService;
    1.14 +import org.mozilla.gecko.background.healthreport.upload.HealthReportUploadService;
    1.15 +import org.mozilla.gecko.background.healthreport.upload.ObsoleteDocumentTracker;
    1.16 +
    1.17 +import android.app.AlarmManager;
    1.18 +import android.app.PendingIntent;
    1.19 +import android.content.Context;
    1.20 +import android.content.Intent;
    1.21 +import android.content.SharedPreferences;
    1.22 +import android.content.SharedPreferences.Editor;
    1.23 +
    1.24 +/**
    1.25 + * A service which listens to broadcast intents from the system and from the
    1.26 + * browser, registering or unregistering the background health report services with the
    1.27 + * {@link AlarmManager}.
    1.28 + */
    1.29 +public class HealthReportBroadcastService extends BackgroundService {
    1.30 +  public static final String LOG_TAG = HealthReportBroadcastService.class.getSimpleName();
    1.31 +  public static final String WORKER_THREAD_NAME = LOG_TAG + "Worker";
    1.32 +
    1.33 +  public HealthReportBroadcastService() {
    1.34 +    super(WORKER_THREAD_NAME);
    1.35 +  }
    1.36 +
    1.37 +  protected SharedPreferences getSharedPreferences() {
    1.38 +    return this.getSharedPreferences(HealthReportConstants.PREFS_BRANCH, GlobalConstants.SHARED_PREFERENCES_MODE);
    1.39 +  }
    1.40 +
    1.41 +  public long getSubmissionPollInterval() {
    1.42 +    return getSharedPreferences().getLong(HealthReportConstants.PREF_SUBMISSION_INTENT_INTERVAL_MSEC, HealthReportConstants.DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC);
    1.43 +  }
    1.44 +
    1.45 +  public void setSubmissionPollInterval(final long interval) {
    1.46 +    getSharedPreferences().edit().putLong(HealthReportConstants.PREF_SUBMISSION_INTENT_INTERVAL_MSEC, interval).commit();
    1.47 +  }
    1.48 +
    1.49 +  public long getPrunePollInterval() {
    1.50 +    return getSharedPreferences().getLong(HealthReportConstants.PREF_PRUNE_INTENT_INTERVAL_MSEC,
    1.51 +        HealthReportConstants.DEFAULT_PRUNE_INTENT_INTERVAL_MSEC);
    1.52 +  }
    1.53 +
    1.54 +  public void setPrunePollInterval(final long interval) {
    1.55 +    getSharedPreferences().edit().putLong(HealthReportConstants.PREF_PRUNE_INTENT_INTERVAL_MSEC,
    1.56 +        interval).commit();
    1.57 +  }
    1.58 +
    1.59 +  /**
    1.60 +   * Set or cancel an alarm to submit data for a profile.
    1.61 +   *
    1.62 +   * @param context
    1.63 +   *          Android context.
    1.64 +   * @param profileName
    1.65 +   *          to submit data for.
    1.66 +   * @param profilePath
    1.67 +   *          to submit data for.
    1.68 +   * @param enabled
    1.69 +   *          whether the user has enabled submitting health report data for
    1.70 +   *          this profile.
    1.71 +   * @param serviceEnabled
    1.72 +   *          whether submitting should be scheduled. If the user turns off
    1.73 +   *          submitting, <code>enabled</code> could be false but we could need
    1.74 +   *          to delete so <code>serviceEnabled</code> could be true.
    1.75 +   */
    1.76 +  protected void toggleSubmissionAlarm(final Context context, String profileName, String profilePath,
    1.77 +      boolean enabled, boolean serviceEnabled) {
    1.78 +    final Class<?> serviceClass = HealthReportUploadService.class;
    1.79 +    Logger.info(LOG_TAG, (serviceEnabled ? "R" : "Unr") + "egistering " +
    1.80 +        serviceClass.getSimpleName() + ".");
    1.81 +
    1.82 +    // PendingIntents are compared without reference to their extras. Therefore
    1.83 +    // even though we pass the profile details to the action, different
    1.84 +    // profiles will share the *same* pending intent. In a multi-profile future,
    1.85 +    // this will need to be addressed.  See Bug 882182.
    1.86 +    final Intent service = new Intent(context, serviceClass);
    1.87 +    service.setAction("upload"); // PendingIntents "lose" their extras if no action is set.
    1.88 +    service.putExtra("uploadEnabled", enabled);
    1.89 +    service.putExtra("profileName", profileName);
    1.90 +    service.putExtra("profilePath", profilePath);
    1.91 +    final PendingIntent pending = PendingIntent.getService(context, 0, service, PendingIntent.FLAG_CANCEL_CURRENT);
    1.92 +
    1.93 +    if (!serviceEnabled) {
    1.94 +      cancelAlarm(pending);
    1.95 +      return;
    1.96 +    }
    1.97 +
    1.98 +    final long pollInterval = getSubmissionPollInterval();
    1.99 +    scheduleAlarm(pollInterval, pending);
   1.100 +  }
   1.101 +
   1.102 +  @Override
   1.103 +  protected void onHandleIntent(Intent intent) {
   1.104 +    Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
   1.105 +
   1.106 +    // Intent can be null. Bug 1025937.
   1.107 +    if (intent == null) {
   1.108 +      Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
   1.109 +      return;
   1.110 +    }
   1.111 +
   1.112 +    // The same intent can be handled by multiple methods so do not short-circuit evaluate.
   1.113 +    boolean handled = attemptHandleIntentForUpload(intent);
   1.114 +    handled = attemptHandleIntentForPrune(intent) ? true : handled;
   1.115 +
   1.116 +    if (!handled) {
   1.117 +      Logger.warn(LOG_TAG, "Unhandled intent with action " + intent.getAction() + ".");
   1.118 +    }
   1.119 +  }
   1.120 +
   1.121 +  /**
   1.122 +   * Attempts to handle the given intent for FHR document upload. If it cannot, false is returned.
   1.123 +   *
   1.124 +   * @param intent must be non-null.
   1.125 +   */
   1.126 +  private boolean attemptHandleIntentForUpload(final Intent intent) {
   1.127 +    if (HealthReportConstants.UPLOAD_FEATURE_DISABLED) {
   1.128 +      Logger.debug(LOG_TAG, "Health report upload feature is compile-time disabled; not handling intent.");
   1.129 +      return false;
   1.130 +    }
   1.131 +
   1.132 +    final String action = intent.getAction();
   1.133 +    Logger.debug(LOG_TAG, "Health report upload feature is compile-time enabled; attempting to " +
   1.134 +        "handle intent with action " + action + ".");
   1.135 +
   1.136 +    if (HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF.equals(action)) {
   1.137 +      handleUploadPrefIntent(intent);
   1.138 +      return true;
   1.139 +    }
   1.140 +
   1.141 +    if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
   1.142 +        Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
   1.143 +      BackgroundService.reflectContextToFennec(this,
   1.144 +          GlobalConstants.GECKO_PREFERENCES_CLASS,
   1.145 +          GlobalConstants.GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD);
   1.146 +      return true;
   1.147 +    }
   1.148 +
   1.149 +    return false;
   1.150 +  }
   1.151 +
   1.152 +  /**
   1.153 +   * Handle the intent sent by the browser when it wishes to notify us
   1.154 +   * of the value of the user preference. Look at the value and toggle the
   1.155 +   * alarm service accordingly.
   1.156 +   *
   1.157 +   * @param intent must be non-null.
   1.158 +   */
   1.159 +  private void handleUploadPrefIntent(Intent intent) {
   1.160 +    if (!intent.hasExtra("enabled")) {
   1.161 +      Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without enabled. Ignoring.");
   1.162 +      return;
   1.163 +    }
   1.164 +
   1.165 +    final boolean enabled = intent.getBooleanExtra("enabled", true);
   1.166 +    Logger.debug(LOG_TAG, intent.getStringExtra("branch") + "/" +
   1.167 +                          intent.getStringExtra("pref")   + " = " +
   1.168 +                          (intent.hasExtra("enabled") ? enabled : ""));
   1.169 +
   1.170 +    String profileName = intent.getStringExtra("profileName");
   1.171 +    String profilePath = intent.getStringExtra("profilePath");
   1.172 +
   1.173 +    if (profileName == null || profilePath == null) {
   1.174 +      Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without profilePath or profileName. Ignoring.");
   1.175 +      return;
   1.176 +    }
   1.177 +
   1.178 +    Logger.pii(LOG_TAG, "Updating health report upload alarm for profile " + profileName + " at " +
   1.179 +        profilePath + ".");
   1.180 +
   1.181 +    final SharedPreferences sharedPrefs = getSharedPreferences();
   1.182 +    final ObsoleteDocumentTracker tracker = new ObsoleteDocumentTracker(sharedPrefs);
   1.183 +    final boolean hasObsoleteIds = tracker.hasObsoleteIds();
   1.184 +
   1.185 +    if (!enabled) {
   1.186 +      final Editor editor = sharedPrefs.edit();
   1.187 +      editor.remove(HealthReportConstants.PREF_LAST_UPLOAD_DOCUMENT_ID);
   1.188 +
   1.189 +      if (hasObsoleteIds) {
   1.190 +        Logger.debug(LOG_TAG, "Health report upload disabled; scheduling deletion of " + tracker.numberOfObsoleteIds() + " documents.");
   1.191 +        tracker.limitObsoleteIds();
   1.192 +      } else {
   1.193 +        // Primarily intended for debugging and testing.
   1.194 +        Logger.debug(LOG_TAG, "Health report upload disabled and no deletes to schedule: clearing prefs.");
   1.195 +        editor.remove(HealthReportConstants.PREF_FIRST_RUN);
   1.196 +        editor.remove(HealthReportConstants.PREF_NEXT_SUBMISSION);
   1.197 +      }
   1.198 +
   1.199 +      editor.commit();
   1.200 +    }
   1.201 +
   1.202 +    // The user can toggle us off or on, or we can have obsolete documents to
   1.203 +    // remove.
   1.204 +    final boolean serviceEnabled = hasObsoleteIds || enabled;
   1.205 +    toggleSubmissionAlarm(this, profileName, profilePath, enabled, serviceEnabled);
   1.206 +  }
   1.207 +
   1.208 +  /**
   1.209 +   * Attempts to handle the given intent for FHR data pruning. If it cannot, false is returned.
   1.210 +   *
   1.211 +   * @param intent must be non-null.
   1.212 +   */
   1.213 +  private boolean attemptHandleIntentForPrune(final Intent intent) {
   1.214 +    final String action = intent.getAction();
   1.215 +    Logger.debug(LOG_TAG, "Prune: Attempting to handle intent with action, " + action + ".");
   1.216 +
   1.217 +    if (HealthReportConstants.ACTION_HEALTHREPORT_PRUNE.equals(action)) {
   1.218 +      handlePruneIntent(intent);
   1.219 +      return true;
   1.220 +    }
   1.221 +
   1.222 +    if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
   1.223 +        Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
   1.224 +      BackgroundService.reflectContextToFennec(this,
   1.225 +          GlobalConstants.GECKO_PREFERENCES_CLASS,
   1.226 +          GlobalConstants.GECKO_BROADCAST_HEALTHREPORT_PRUNE_METHOD);
   1.227 +      return true;
   1.228 +    }
   1.229 +
   1.230 +    return false;
   1.231 +  }
   1.232 +
   1.233 +  /**
   1.234 +   * @param intent must be non-null.
   1.235 +   */
   1.236 +  private void handlePruneIntent(final Intent intent) {
   1.237 +    final String profileName = intent.getStringExtra("profileName");
   1.238 +    final String profilePath = intent.getStringExtra("profilePath");
   1.239 +
   1.240 +    if (profileName == null || profilePath == null) {
   1.241 +      Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_PRUNE + " intent " +
   1.242 +          "without profilePath or profileName. Ignoring.");
   1.243 +      return;
   1.244 +    }
   1.245 +
   1.246 +    final Class<?> serviceClass = HealthReportPruneService.class;
   1.247 +    final Intent service = new Intent(this, serviceClass);
   1.248 +    service.setAction("prune"); // Intents without actions have their extras removed.
   1.249 +    service.putExtra("profileName", profileName);
   1.250 +    service.putExtra("profilePath", profilePath);
   1.251 +    final PendingIntent pending = PendingIntent.getService(this, 0, service,
   1.252 +        PendingIntent.FLAG_CANCEL_CURRENT);
   1.253 +
   1.254 +    // Set a regular alarm to start PruneService. Since the various actions that PruneService can
   1.255 +    // take occur on irregular intervals, we can be more efficient by only starting the Service
   1.256 +    // when one of these time limits runs out.  However, subsequent Service invocations must then
   1.257 +    // be registered by the PruneService itself, which would fail if the PruneService crashes.
   1.258 +    // Thus, we set this regular (and slightly inefficient) alarm.
   1.259 +    Logger.info(LOG_TAG, "Registering " + serviceClass.getSimpleName() + ".");
   1.260 +    final long pollInterval = getPrunePollInterval();
   1.261 +    scheduleAlarm(pollInterval, pending);
   1.262 +  }
   1.263 +}

mercurial