Java tutorial
/* 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; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.sync.setup.SyncAccounts; import android.accounts.Account; import android.accounts.AccountManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.support.v4.content.AsyncTaskLoader; /** * A Loader that queries and updates based on the existence of Firefox and * legacy Sync Android Accounts. * * The loader returns an Android Account (of either Account type) if an account * exists, and null to indicate no Account is present. * * The loader listens for Accounts added and deleted, and also Accounts being * updated by Sync or another Activity, via the use of * {@link AndroidFxAccount#setState(org.mozilla.gecko.fxa.login.State)}. * Be careful of message loops if you update the account state from an activity * that uses this loader. * * This implementation is based on * <a href="http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html">http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html</a>. */ public class AccountLoader extends AsyncTaskLoader<Account> { protected Account account = null; protected BroadcastReceiver broadcastReceiver = null; public AccountLoader(Context context) { super(context); } // Task that performs the asynchronous load **/ @Override public Account loadInBackground() { final Context context = getContext(); Account foundAccount = FirefoxAccounts.getFirefoxAccount(context); if (foundAccount == null) { final Account[] syncAccounts = SyncAccounts.syncAccounts(context); if (syncAccounts != null && syncAccounts.length > 0) { foundAccount = syncAccounts[0]; } } return foundAccount; } // Deliver the results to the registered listener. @Override public void deliverResult(Account data) { if (isReset()) { // The Loader has been reset; ignore the result and invalidate the data. releaseResources(data); return; } // Hold a reference to the old data so it doesn't get garbage collected. // We must protect it until the new data has been delivered. Account oldData = account; account = data; if (isStarted()) { // If the Loader is in a started state, deliver the results to the // client. The superclass method does this for us. super.deliverResult(data); } // Invalidate the old data as we don't need it any more. if (oldData != null && oldData != data) { releaseResources(oldData); } } // The Loaders state-dependent behavior. @Override protected void onStartLoading() { if (account != null) { // Deliver any previously loaded data immediately. deliverResult(account); } // Begin monitoring the underlying data source. if (broadcastReceiver == null) { broadcastReceiver = makeNewObserver(); registerObserver(broadcastReceiver); } if (takeContentChanged() || account == null) { // When the observer detects a change, it should call onContentChanged() // on the Loader, which will cause the next call to takeContentChanged() // to return true. If this is ever the case (or if the current data is // null), we force a new load. forceLoad(); } } @Override protected void onStopLoading() { // The Loader is in a stopped state, so we should attempt to cancel the // current load (if there is one). cancelLoad(); // Note that we leave the observer as is. Loaders in a stopped state // should still monitor the data source for changes so that the Loader // will know to force a new load if it is ever started again. } @Override protected void onReset() { // Ensure the loader has been stopped. In CursorLoader and the template // this code follows (see the class comment), this is onStopLoading, which // appears to not set the started flag (see Loader itself). stopLoading(); // At this point we can release the resources associated with 'mData'. if (account != null) { releaseResources(account); account = null; } // The Loader is being reset, so we should stop monitoring for changes. if (broadcastReceiver != null) { final BroadcastReceiver observer = broadcastReceiver; broadcastReceiver = null; unregisterObserver(observer); } } @Override public void onCanceled(Account data) { // Attempt to cancel the current asynchronous load. super.onCanceled(data); // The load has been canceled, so we should release the resources // associated with 'data'. releaseResources(data); } private void releaseResources(Account data) { // For a simple List, there is nothing to do. For something like a Cursor, we // would close it in this method. All resources associated with the Loader // should be released here. } // Observer which receives notifications when the data changes. protected BroadcastReceiver makeNewObserver() { final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Must be called on the main thread of the process. We register the // broadcast receiver with a null Handler (see registerObserver), which // ensures we're on the main thread when we receive this intent. onContentChanged(); } }; return broadcastReceiver; } protected void registerObserver(BroadcastReceiver observer) { final IntentFilter intentFilter = new IntentFilter(); // Android Account added or removed. intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); // Firefox Account internal state changed. intentFilter.addAction(FxAccountConstants.ACCOUNT_STATE_CHANGED_ACTION); // null means: "the main thread of the process will be used." We must call // onContentChanged on the main thread of the process; this ensures we do. final Handler handler = null; getContext().registerReceiver(observer, intentFilter, FxAccountConstants.PER_ACCOUNT_TYPE_PERMISSION, handler); } protected void unregisterObserver(BroadcastReceiver observer) { getContext().unregisterReceiver(observer); } }