summaryrefslogtreecommitdiffstats
path: root/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountUpgradeReceiver.java
blob: ad81e04886304cd928c979fe91d401a98f1a0405 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.fxa.receivers;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.sync.Utils;

import android.accounts.Account;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

/**
 * A receiver that takes action when our Android package is upgraded (replaced).
 */
public class FxAccountUpgradeReceiver extends BroadcastReceiver {
  private static final String LOG_TAG = FxAccountUpgradeReceiver.class.getSimpleName();

  /**
   * Produce a list of Runnable instances to be executed sequentially on
   * upgrade.
   * <p>
   * Each Runnable will be executed sequentially on a background thread. Any
   * unchecked Exception thrown will be caught and ignored.
   *
   * @param context Android context.
   * @return list of Runnable instances.
   */
  protected List<Runnable> onUpgradeRunnables(Context context) {
    List<Runnable> runnables = new LinkedList<Runnable>();
    runnables.add(new MaybeUnpickleRunnable(context));
    // Recovering accounts that are in the Doghouse should happen *after* we
    // unpickle any accounts saved to disk.
    runnables.add(new AdvanceFromDoghouseRunnable(context));
    return runnables;
  }

  @Override
  public void onReceive(final Context context, Intent intent) {
    Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
    Logger.info(LOG_TAG, "Upgrade broadcast received.");

    // Iterate Runnable instances one at a time.
    final Executor executor = Executors.newSingleThreadExecutor();
    for (final Runnable runnable : onUpgradeRunnables(context)) {
      executor.execute(new Runnable() {
        @Override
        public void run() {
          try {
            runnable.run();
          } catch (Exception e) {
            // We really don't want to throw on a background thread, so we
            // catch, log, and move on.
            Logger.error(LOG_TAG, "Got exception executing background upgrade Runnable; ignoring.", e);
          }
        }
      });
    }
  }

  /**
   * A Runnable that tries to unpickle any pickled Firefox Accounts.
   */
  protected static class MaybeUnpickleRunnable implements Runnable {
    protected final Context context;

    public MaybeUnpickleRunnable(Context context) {
      this.context = context;
    }

    @Override
    public void run() {
      // Querying the accounts will unpickle any pickled Firefox Account.
      Logger.info(LOG_TAG, "Trying to unpickle any pickled Firefox Account.");
      FirefoxAccounts.getFirefoxAccounts(context);
    }
  }

  /**
   * A Runnable that tries to advance existing Firefox Accounts that are in the
   * Doghouse state to the Separated state.
   * <p>
   * This is our main deprecation-and-upgrade mechanism: in some way, the
   * Account gets moved to the Doghouse state. If possible, an upgraded version
   * of the package advances to Separated, prompting the user to re-connect the
   * Account.
   */
  protected static class AdvanceFromDoghouseRunnable implements Runnable {
    protected final Context context;

    public AdvanceFromDoghouseRunnable(Context context) {
      this.context = context;
    }

    @Override
    public void run() {
      final Account[] accounts = FirefoxAccounts.getFirefoxAccounts(context);
      Logger.info(LOG_TAG, "Trying to advance " + accounts.length + " existing Firefox Accounts from the Doghouse to Separated (if necessary).");
      for (Account account : accounts) {
        try {
          final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
          // For great debugging.
          if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
            fxAccount.dump();
          }
          State state = fxAccount.getState();
          if (state == null || state.getStateLabel() != StateLabel.Doghouse) {
            Logger.debug(LOG_TAG, "Account named like " + Utils.obfuscateEmail(account.name) + " is not in the Doghouse; skipping.");
            continue;
          }
          Logger.debug(LOG_TAG, "Account named like " + Utils.obfuscateEmail(account.name) + " is in the Doghouse; advancing to Separated.");
          fxAccount.setState(state.makeSeparatedState());
        } catch (Exception e) {
          Logger.warn(LOG_TAG, "Got exception trying to advance account named like " + Utils.obfuscateEmail(account.name) +
              " from Doghouse to Separated state; ignoring.", e);
        }
      }
    }
  }
}