michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.fxa.receivers; michael@0: michael@0: import java.util.LinkedList; michael@0: import java.util.List; michael@0: import java.util.concurrent.Executor; michael@0: import java.util.concurrent.Executors; michael@0: michael@0: import org.mozilla.gecko.background.common.log.Logger; michael@0: import org.mozilla.gecko.fxa.FirefoxAccounts; michael@0: import org.mozilla.gecko.fxa.FxAccountConstants; michael@0: import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; michael@0: import org.mozilla.gecko.fxa.login.State; michael@0: import org.mozilla.gecko.fxa.login.State.StateLabel; michael@0: import org.mozilla.gecko.sync.Utils; michael@0: michael@0: import android.accounts.Account; michael@0: import android.content.BroadcastReceiver; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: michael@0: /** michael@0: * A receiver that takes action when our Android package is upgraded (replaced). michael@0: */ michael@0: public class FxAccountUpgradeReceiver extends BroadcastReceiver { michael@0: private static final String LOG_TAG = FxAccountUpgradeReceiver.class.getSimpleName(); michael@0: michael@0: /** michael@0: * Produce a list of Runnable instances to be executed sequentially on michael@0: * upgrade. michael@0: *

michael@0: * Each Runnable will be executed sequentially on a background thread. Any michael@0: * unchecked Exception thrown will be caught and ignored. michael@0: * michael@0: * @param context Android context. michael@0: * @return list of Runnable instances. michael@0: */ michael@0: protected List onUpgradeRunnables(Context context) { michael@0: List runnables = new LinkedList(); michael@0: runnables.add(new MaybeUnpickleRunnable(context)); michael@0: // Recovering accounts that are in the Doghouse should happen *after* we michael@0: // unpickle any accounts saved to disk. michael@0: runnables.add(new AdvanceFromDoghouseRunnable(context)); michael@0: return runnables; michael@0: } michael@0: michael@0: @Override michael@0: public void onReceive(final Context context, Intent intent) { michael@0: Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG); michael@0: Logger.info(LOG_TAG, "Upgrade broadcast received."); michael@0: michael@0: // Iterate Runnable instances one at a time. michael@0: final Executor executor = Executors.newSingleThreadExecutor(); michael@0: for (final Runnable runnable : onUpgradeRunnables(context)) { michael@0: executor.execute(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: runnable.run(); michael@0: } catch (Exception e) { michael@0: // We really don't want to throw on a background thread, so we michael@0: // catch, log, and move on. michael@0: Logger.error(LOG_TAG, "Got exception executing background upgrade Runnable; ignoring.", e); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * A Runnable that tries to unpickle any pickled Firefox Accounts. michael@0: */ michael@0: protected static class MaybeUnpickleRunnable implements Runnable { michael@0: protected final Context context; michael@0: michael@0: public MaybeUnpickleRunnable(Context context) { michael@0: this.context = context; michael@0: } michael@0: michael@0: @Override michael@0: public void run() { michael@0: // Querying the accounts will unpickle any pickled Firefox Account. michael@0: Logger.info(LOG_TAG, "Trying to unpickle any pickled Firefox Account."); michael@0: FirefoxAccounts.getFirefoxAccounts(context); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * A Runnable that tries to advance existing Firefox Accounts that are in the michael@0: * Doghouse state to the Separated state. michael@0: *

michael@0: * This is our main deprecation-and-upgrade mechanism: in some way, the michael@0: * Account gets moved to the Doghouse state. If possible, an upgraded version michael@0: * of the package advances to Separated, prompting the user to re-connect the michael@0: * Account. michael@0: */ michael@0: protected static class AdvanceFromDoghouseRunnable implements Runnable { michael@0: protected final Context context; michael@0: michael@0: public AdvanceFromDoghouseRunnable(Context context) { michael@0: this.context = context; michael@0: } michael@0: michael@0: @Override michael@0: public void run() { michael@0: final Account[] accounts = FirefoxAccounts.getFirefoxAccounts(context); michael@0: Logger.info(LOG_TAG, "Trying to advance " + accounts.length + " existing Firefox Accounts from the Doghouse to Separated (if necessary)."); michael@0: for (Account account : accounts) { michael@0: try { michael@0: final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); michael@0: // For great debugging. michael@0: if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { michael@0: fxAccount.dump(); michael@0: } michael@0: State state = fxAccount.getState(); michael@0: if (state == null || state.getStateLabel() != StateLabel.Doghouse) { michael@0: Logger.debug(LOG_TAG, "Account named like " + Utils.obfuscateEmail(account.name) + " is not in the Doghouse; skipping."); michael@0: continue; michael@0: } michael@0: Logger.debug(LOG_TAG, "Account named like " + Utils.obfuscateEmail(account.name) + " is in the Doghouse; advancing to Separated."); michael@0: fxAccount.setState(state.makeSeparatedState()); michael@0: } catch (Exception e) { michael@0: Logger.warn(LOG_TAG, "Got exception trying to advance account named like " + Utils.obfuscateEmail(account.name) + michael@0: " from Doghouse to Separated state; ignoring.", e); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: }