org.mozilla.gecko.home.RemoteTabsPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.mozilla.gecko.home.RemoteTabsPanel.java

Source

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * 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.home;

import org.mozilla.gecko.GeckoScreenOrientation;
import org.mozilla.gecko.R;
import org.mozilla.gecko.fxa.AccountLoader;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.Action;
import org.mozilla.gecko.sync.SyncConstants;
import org.mozilla.gecko.util.HardwareUtils;

import android.accounts.Account;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.util.Pair;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * A <code>HomeFragment</code> that, depending on the state of accounts on the
 * device:
 * <ul>
 * <li>displays remote tabs from other devices;</li>
 * <li>offers to re-connect a Firefox Account;</li>
 * <li>offers to create a new Firefox Account.</li>
 * </ul>
 */
public class RemoteTabsPanel extends HomeFragment {
    private static final String LOGTAG = "GeckoRemoteTabsPanel";

    // Loader ID for Android Account loader.
    private static final int LOADER_ID_ACCOUNT = 0;
    private static final String FRAGMENT_ACTION = "FRAGMENT_ACTION";
    private static final String FRAGMENT_ORIENTATION = "FRAGMENT_ORIENTATION";
    private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
    private static final String NO_ACCOUNT = "NO_ACCOUNT";

    // Callback for loaders.
    private AccountLoaderCallbacks mAccountLoaderCallbacks;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.home_remote_tabs_panel, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Create callbacks before the initial loader is started.
        mAccountLoaderCallbacks = new AccountLoaderCallbacks();
        loadIfVisible();
    }

    @Override
    protected void loadIfVisible() {
        // Force reload fragment only in tablets when there is valid account and the orientation has changed.
        Pair<String, Integer> actionOrientationPair;
        if (canLoad() && HardwareUtils.isTablet()
                && (actionOrientationPair = getActionAndOrientationForFragmentInBackStack()) != null) {
            if (actionOrientationPair.first.equals(Action.None.name())
                    && actionOrientationPair.second != GeckoScreenOrientation.getInstance()
                            .getAndroidOrientation()) {
                // As the fragment becomes visible only after onStart callback, we can safely remove it from the back-stack.
                // If a portrait fragment is in the back-stack and then a landscape fragment should be shown, there can
                // be a brief flash as the fragment as replaced.
                getChildFragmentManager().beginTransaction().addToBackStack(null)
                        .remove(getChildFragmentManager().findFragmentByTag(FRAGMENT_TAG))
                        .commitAllowingStateLoss();
                getChildFragmentManager().executePendingTransactions();

                load();
                return;
            }
        }
        super.loadIfVisible();
    }

    @Override
    public void load() {
        getLoaderManager().initLoader(LOADER_ID_ACCOUNT, null, mAccountLoaderCallbacks);
    }

    private void showSubPanel(Account account) {
        final Action actionNeeded = getActionNeeded(account);
        final String actionString = actionNeeded != null ? actionNeeded.name() : NO_ACCOUNT;
        final int orientation = HardwareUtils.isTablet()
                ? GeckoScreenOrientation.getInstance().getAndroidOrientation()
                : Configuration.ORIENTATION_UNDEFINED;

        // Check if fragment for given action and orientation is in the back-stack.
        final Pair<String, Integer> actionOrientationPair = getActionAndOrientationForFragmentInBackStack();
        if (actionOrientationPair != null && actionOrientationPair.first.equals(actionString)
                && (actionOrientationPair.second == orientation)) {
            return;
        }

        // Instantiate the fragment for the action and update the arguments.
        Fragment subPanel = makeFragmentForAction(actionNeeded);
        final Bundle args = new Bundle();
        args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
        args.putString(FRAGMENT_ACTION, actionString);
        args.putInt(FRAGMENT_ORIENTATION, orientation);
        subPanel.setArguments(args);

        // Add the fragment to the back-stack.
        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .replace(R.id.remote_tabs_container, subPanel, FRAGMENT_TAG).commitAllowingStateLoss();
    }

    private Pair<String, Integer> getActionAndOrientationForFragmentInBackStack() {
        final Fragment currentFragment = getChildFragmentManager().findFragmentByTag(FRAGMENT_TAG);
        if (currentFragment != null && currentFragment.getArguments() != null) {
            final String fragmentAction = currentFragment.getArguments().getString(FRAGMENT_ACTION);
            final int fragmentOrientation = currentFragment.getArguments().getInt(FRAGMENT_ORIENTATION);
            return Pair.create(fragmentAction, fragmentOrientation);
        }
        return null;
    }

    /**
     * Get whatever <code>Action</code> is required to continue healthy syncing
     * of Remote Tabs.
     * <p>
     * A Firefox Account can be in many states, from healthy to requiring a
     * Fennec upgrade to continue use. If we have a Firefox Account, but the
     * state seems corrupt, the best we can do is ask for a password, which
     * resets most of the Account state. The health of a Sync account is
     * essentially opaque in this respect.
     * <p>
     * A null Account means there is no Account (Sync or Firefox) on the device.
     *
     * @param account
     *            Android Account (Sync or Firefox); may be null.
     */
    private Action getActionNeeded(Account account) {
        if (account == null) {
            return null;
        }

        if (SyncConstants.ACCOUNTTYPE_SYNC.equals(account.type)) {
            return Action.None;
        }

        if (!FxAccountConstants.ACCOUNT_TYPE.equals(account.type)) {
            Log.wtf(LOGTAG, "Non Sync, non Firefox Android Account returned by AccountLoader; returning null.");
            return null;
        }

        final State state = FirefoxAccounts.getFirefoxAccountState(getActivity());
        if (state == null) {
            Log.wtf(LOGTAG, "Firefox Account with null state found; offering needs password.");
            return Action.NeedsPassword;
        }

        final Action actionNeeded = state.getNeededAction();
        if (actionNeeded == null) {
            Log.wtf(LOGTAG, "Firefox Account with non-null state but null action needed; offering needs password.");
            return Action.NeedsPassword;
        }

        return actionNeeded;
    }

    private Fragment makeFragmentForAction(Action action) {
        if (action == null) {
            // This corresponds to no Account: neither Sync nor Firefox.
            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_setup);
        }

        switch (action) {
        case None:
            if (HardwareUtils.isTablet() && GeckoScreenOrientation.getInstance()
                    .getAndroidOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
                return new RemoteTabsSplitPlaneFragment();
            } else {
                return new RemoteTabsExpandableListFragment();
            }
        case NeedsVerification:
            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_verification);
        case NeedsPassword:
            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
        case NeedsUpgrade:
            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_upgrade);
        case NeedsFinishMigrating:
            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_finish_migrating);
        default:
            // This should never happen, but we're confident we have a Firefox
            // Account at this point, so let's show the needs password screen.
            // That's our best hope of righting the ship.
            Log.wtf(LOGTAG, "Got unexpected action needed; offering needs password.");
            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
        }
    }

    /**
     * Update the UI to reflect the given <code>Account</code> and its state.
     * <p>
     * A null Account means there is no Account (Sync or Firefox) on the device.
     *
     * @param account
     *            Android Account (Sync or Firefox); may be null.
     */
    protected void updateUiFromAccount(Account account) {
        if (getView() == null) {
            // Early abort. When the fragment is detached, we get a loader
            // reset, which calls this with a null account parameter. A null
            // account is valid (it means there is no account, either Sync or
            // Firefox), and so we start to offer the setup flow. But this all
            // happens after the view has been destroyed, which means inserting
            // the setup flow fails. In this case, just abort.
            return;
        }
        showSubPanel(account);
    }

    private class AccountLoaderCallbacks implements LoaderCallbacks<Account> {
        @Override
        public Loader<Account> onCreateLoader(int id, Bundle args) {
            return new AccountLoader(getActivity());
        }

        @Override
        public void onLoadFinished(Loader<Account> loader, Account account) {
            updateUiFromAccount(account);
        }

        @Override
        public void onLoaderReset(Loader<Account> loader) {
            updateUiFromAccount(null);
        }
    }
}