info.icefilms.icestream.browse.BrowseFragment.java Source code

Java tutorial

Introduction

Here is the source code for info.icefilms.icestream.browse.BrowseFragment.java

Source

/*
 * IceStream - The Official IceFilms Android Application
 * Copyright (C) 2011-2013 Stonyx
 *
 * The IceStream application (and any "covered work" as defined by the GNU General Public
 * License Version 3) is licensed under the GNU General Public License Version 3 (henceforth
 * referred to as "GNU GPL V3") with the following amendments that supersede any relevant wording
 * in the GNU GPL V3:
 *
 * 1. The IceStream application (and any "covered work" as defined by the GNU GPL V3) can be
 *    statically and/or dynamically linked to any source code, library, or application developed
 *    or released by Stonyx (the original authors of the IceStream application), regardless of the
 *    type of license that such source code, library, or application is licensed under.
 *
 * 2. The IceStream application (and any "covered work" as defined by the GNU GPL V3) can not be
 *    distributed for a fee without the prior written consent provided by Stonyx (the original
 *    authors of the IceStream application).
 *
 * The preceding amendments make up part of the license that the IceStream application is licensed
 * under.  They apply to and need to be included (along with the GNU GPL V3) with any derivative
 * work as outlined in the GNU GPL V3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU GPL V3 for more details.
 *
 * You should have received a copy of the GNU GPL V3 along with this program.  If not, see
 * http://www.gnu.org/licenses/.
 */

package info.icefilms.icestream.browse;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Toast;
import info.icefilms.icestream.R;
import com.stonyx.megastream.open.OpenActivity;
import com.stonyx.ads.AdLayout;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class BrowseFragment extends Fragment {
    // Variables
    private LinearLayout[] mHeadingLayout;
    private HorizontalListView[] mHeading;
    private ArrayList<Item>[] mHeadingItems;
    private ItemArrayAdapter[] mHeadingAdapter;
    private ImageView mDivider;
    private ListView mList;
    private ArrayList<Item> mListItems;
    private ItemArrayAdapter mListAdapter;
    private AdLayout mAdLayout;
    private Boolean mInitialConnection;
    private LinkedList<State> mCurrentState;
    private LinearLayout mRetryLayout;
    private Button mRetryButton;
    private LoadStateTask mLoadStateTask;
    private OpenSourceLocationTask mOpenSourceLocationTask;
    private ProgressDialog mProgressDialog;
    private AsyncTask<?, ?, ?> mProgressDialogTask;
    private Boolean mProgressDialogShowing;
    private AlertDialog mActionDialog;
    private URL mActionDialogURL;
    private StopParcel mStopParcel;

    // Class used to store details about our current state
    private static class State implements Parcelable {
        // Variables
        private Location mLocation;
        private ArrayList<Item>[] mHeadingItems;
        private ArrayList<Item> mListItems;
        private int mListScrollPosition;
        private int mListScrollOffset;

        // Constructor
        protected State(Location location) {
            mLocation = location;
        }

        // Constructor used by the parcelable functionality of this class
        @SuppressWarnings("unchecked")
        protected State(Parcel in) {
            mLocation = in.readParcelable(null);

            int size = in.readInt();
            if (size != 0) {
                mHeadingItems = new ArrayList[size];
                Bundle bundle = in.readBundle();
                for (int i = 0; i < mHeadingItems.length; ++i)
                    mHeadingItems[i] = bundle.getParcelableArrayList("HeadingItems" + i);
            }

            size = in.readInt();
            if (size != 0) {
                Bundle bundle = in.readBundle();
                mListItems = bundle.getParcelableArrayList("ListItems");
            }

            mListScrollPosition = in.readInt();
            mListScrollOffset = in.readInt();
        }

        // Variable used by the parcelable functionality of this class
        @SuppressWarnings("unused")
        public static final Parcelable.Creator<State> CREATOR = new Parcelable.Creator<State>() {
            public State createFromParcel(Parcel in) {
                return new State(in);
            }

            public State[] newArray(int size) {
                return new State[size];
            }
        };

        // Method used by the parcelable functionality of this class
        public int describeContents() {
            return 0;
        }

        // Method used by the parcelable functionality of this class
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeParcelable(mLocation, flags);

            if (mHeadingItems == null) {
                dest.writeInt(0);
            } else {
                dest.writeInt(mHeadingItems.length);
                Bundle bundle = new Bundle();
                for (int i = 0; i < mHeadingItems.length; ++i)
                    bundle.putParcelableArrayList("HeadingItems" + i, mHeadingItems[i]);
                dest.writeBundle(bundle);
            }

            if (mListItems == null) {
                dest.writeInt(0);
            } else {
                dest.writeInt(1);
                Bundle bundle = new Bundle();
                bundle.putParcelableArrayList("ListItems", mListItems);
                dest.writeBundle(bundle);
            }

            dest.writeInt(mListScrollPosition);
            dest.writeInt(mListScrollOffset);
        }

        // Setter and getter methods
        protected Location GetLocation() {
            return mLocation;
        }

        protected void SetHeadingItems(ArrayList<Item>[] items) {
            mHeadingItems = items;
        }

        protected ArrayList<Item>[] GetHeadingItems() {
            return mHeadingItems;
        }

        protected void SetListItems(ArrayList<Item> items) {
            mListItems = items;
        }

        protected ArrayList<Item> GetListItems() {
            return mListItems;
        }

        protected void SetListScrollPosition(int position) {
            mListScrollPosition = position;
        }

        protected int GetListScrollPosition() {
            return mListScrollPosition;
        }

        protected void SetListScrollOffset(int offset) {
            mListScrollOffset = offset;
        }

        protected int GetListScrollOffset() {
            return mListScrollOffset;
        }
    }

    // Method called to start a new OpenLocationTask task if one isn't already running
    private void StartLoadStateTask(final State state) {
        // Check if we should add this state to the current state list
        if (mCurrentState.isEmpty() || mCurrentState.getLast() != state) {
            // Check if we should save the previous state scroll position
            if (mCurrentState.isEmpty() == false) {
                // Calculate the scroll position
                int position = mList.getFirstVisiblePosition();
                View top = mList.getChildAt(0);
                int offset = (top == null) ? 0 : top.getTop();

                // Save the position
                State last = mCurrentState.getLast();
                last.SetListScrollPosition(position);
                last.SetListScrollOffset(offset);
            }

            // Add the state to the current state list
            mCurrentState.add(state);
            Log.d("Ice Stream", "New state added to current state list.");

            // Keep the list to less than 20
            if (mCurrentState.size() > 20)
                mCurrentState.removeFirst();
            Log.d("Ice Stream", "Current state list contains " + mCurrentState.size() + " state(s).");
        }

        // Check if we already have heading and list items
        if (state.GetHeadingItems() != null && state.GetListItems() != null) {
            // Load the items
            LoadStateItems(state);

            // Restore the scroll position
            new Handler().postDelayed(new Runnable() {
                public void run() {
                    mList.setSelectionFromTop(state.GetListScrollPosition(), state.GetListScrollOffset());
                    Log.d("Ice Stream", "Set list to scroll position " + state.GetListScrollPosition()
                            + " & offset " + state.GetListScrollOffset() + ".");
                }
            }, 100);
        } else if (mLoadStateTask == null || (mLoadStateTask != null
                && (mLoadStateTask.isCancelled() || mLoadStateTask.getStatus() == AsyncTask.Status.FINISHED))) {
            // Create and run the new task
            mLoadStateTask = new LoadStateTask();
            mLoadStateTask.execute(state);
        }
    }

    // Class responsible for retrieving information on a separate thread
    private class LoadStateTask extends AsyncTask<State, Void, State> {
        // Overridden method that runs before the onInBackground method
        @Override
        protected void onPreExecute() {
            // Show the progress dialog
            ShowProgressDialog(this);
        }

        // Overridden method that does all the work
        @Override
        protected State doInBackground(State... params) {
            // Get the activity
            Activity activity = GetActivity();
            if (activity == null)
                return null;

            // Create the callback object
            final Location.Callback callback = new Location.Callback(activity, this);

            // Get the heading items
            Log.d("Ice Stream", "Attempting to retrieve headings.");
            ArrayList<Item>[] headingItems = params[0].GetLocation().GetHeadingItems(callback);
            if (headingItems == null) {
                if (callback.IsCancelled()) {
                    Log.d("Ice Stream", "Async Task cancelled, no headings retrieved.");
                    return null;
                }
            } else {
                Log.d("Ice Stream", "Retrieved " + headingItems.length + " heading(s).");
            }

            // Get a list of the list items
            ArrayList<Item> listItems = null;
            if (headingItems != null) {
                Log.d("Ice Stream", "Attempting to retrieve list items.");
                listItems = params[0].GetLocation().GetListItems(callback);
                if (listItems == null) {
                    if (callback.IsCancelled()) {
                        Log.d("Ice Stream", "Async Task cancelled, no list items retrieved.");
                        return null;
                    }
                } else {
                    Log.d("Ice Stream", "Retieved " + listItems.size() + " list item(s).");
                }
            }

            // Check for warnings and errors
            if (callback.GetWarningBoolean() == true || callback.GetErrorBoolean() == true) {
                if (callback.GetWarningBoolean() == true) {
                    Log.w("Ice Stream", "Warning occurred while retrieving headings or list items. "
                            + "Warning toast message: " + getString(callback.GetWarningStringID()));
                }

                if (callback.GetErrorBoolean() == true) {
                    if (headingItems == null)
                        Log.e("Ice Stream", "Error retrieving headings. Error dialog message: "
                                + getString(callback.GetErrorStringID()));
                    else if (listItems == null)
                        Log.e("Ice Stream", "Error retrieving list items. Error dialog message: "
                                + getString(callback.GetErrorStringID()));
                }

                // Display the warning toast and/or error dialog
                getActivity().runOnUiThread(new Runnable() {
                    public void run() {
                        Activity activity = GetActivity();
                        if (callback.IsCancelled() == false && activity != null
                                && activity.isFinishing() == false) {
                            if (callback.GetWarningBoolean() == true) {
                                // Create and display a toast
                                Toast toast = Toast.makeText(getActivity(), callback.GetWarningStringID(),
                                        Toast.LENGTH_LONG);
                                toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 96);
                                toast.show();
                            }

                            if (callback.GetErrorBoolean() == true) {
                                // Create and display an error dialog
                                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                                builder.setMessage(callback.GetErrorStringID());
                                builder.setPositiveButton("OK", null);
                                builder.setCancelable(true);
                                builder.create().show();
                            }
                        }
                    }
                });

                if (callback.GetErrorBoolean() == true)
                    return null;
            }

            // Save the items in the state and return it
            params[0].SetHeadingItems(headingItems);
            params[0].SetListItems(listItems);
            return params[0];
        }

        // Overridden method called when this task gets cancelled
        @Override
        protected void onCancelled() {
            // Remove the last state added to the current state list
            // TODO: One error received from this line of code - NoSuchElementException
            mCurrentState.removeLast();
            Log.d("Ice Stream", "Last state removed from current state list.");

            // Check if this is the initial connection that was cancelled and show
            //    the retry button
            if (mInitialConnection == true) {
                mInitialConnection = false;
                mRetryLayout.setVisibility(View.VISIBLE);
            }
        }

        // Overridden method that runs after the onInBackground method
        @Override
        protected void onPostExecute(State result) {
            // Check for any errors
            if (result == null) {
                // Run the onCancelled method
                onCancelled();

                // Dismiss the progress dialog
                DismissProgressDialog();

                return;
            }

            // Load the state items
            LoadStateItems(result);

            // Scroll back to the top
            new Handler().postDelayed(new Runnable() {
                public void run() {
                    mList.setSelection(0);
                    Log.d("Ice Stream", "Set list to scroll position 0.");
                }
            }, 100);

            // Clear the initial connection boolean
            if (mInitialConnection == true)
                mInitialConnection = false;

            // Hide the retry button
            if (mRetryLayout.getVisibility() == View.VISIBLE)
                mRetryLayout.setVisibility(View.GONE);

            // Dismiss the progress dialog
            DismissProgressDialog();
        }
    }

    // Method called to load the actual state items
    private void LoadStateItems(State state) {
        // Loop thru the heading items
        for (int i = 0; i < mHeadingItems.length; ++i) {
            // Clear the heading items
            mHeadingItems[i].clear();

            if (i < state.GetHeadingItems().length) {
                // Add the heading items from the state
                mHeadingItems[i].addAll(state.GetHeadingItems()[i]);

                // Show this heading
                if (mHeadingLayout[i].getVisibility() == View.GONE)
                    mHeadingLayout[i].setVisibility(View.VISIBLE);
            } else {
                // Hide this heading
                if (mHeadingLayout[i].getVisibility() == View.VISIBLE)
                    mHeadingLayout[i].setVisibility(View.GONE);
            }

            // Notify the adapter that we changed the list
            mHeadingAdapter[i].notifyDataSetChanged();
        }

        // Show the divider
        if (mDivider.getVisibility() == View.GONE)
            mDivider.setVisibility(View.VISIBLE);

        // Clear the list items
        mListItems.clear();

        // Add the list items from the result array
        mListItems.addAll(state.GetListItems());

        // Notify the adapter that we changed the list
        mListAdapter.notifyDataSetChanged();
    }

    // Method called to start a new OpenSourceLocationTask task if one isn't already running
    private void StartOpenSourceLocationTask(SourceLocation location) {
        // Create and run the new task
        if (mOpenSourceLocationTask == null
                || (mOpenSourceLocationTask != null && (mOpenSourceLocationTask.isCancelled()
                        || mOpenSourceLocationTask.getStatus() == AsyncTask.Status.FINISHED))) {
            mOpenSourceLocationTask = new OpenSourceLocationTask();
            mOpenSourceLocationTask.execute(location);
        }
    }

    // Class responsible for open a source location on a separate thread
    private class OpenSourceLocationTask extends AsyncTask<SourceLocation, Void, URL> {
        // Overridden method that runs before the onInBackground method
        @Override
        protected void onPreExecute() {
            // Show the progress dialog
            ShowProgressDialog(this);
        }

        // Overridden method that does all the work
        @Override
        protected URL doInBackground(SourceLocation... params) {
            // Get the activity
            Activity activity = GetActivity();
            if (activity == null)
                return null;

            // Create the callback object
            final Location.Callback callback = new Location.Callback(activity, this);

            // Get the actual URL for this source
            Log.d("Ice Stream", "Attempting to retrieve source URL.");
            URL url = params[0].GetURL(callback);
            if (url == null) {
                if (callback.IsCancelled()) {
                    Log.d("Ice Stream", "Async Task cancelled, no source URL retrieved.");
                    return null;
                }
            } else {
                Log.d("Ice Stream", "Retrieved source URL: " + url.toString());
            }

            // Check for warnings and errors
            if (callback.GetWarningBoolean() == true || callback.GetErrorBoolean() == true) {
                if (callback.GetWarningBoolean() == true)
                    Log.w("Ice Stream", "Warning occurred while retrieving source URL. " + "Warning toast message: "
                            + getString(callback.GetWarningStringID()));

                if (callback.GetErrorBoolean() == true)
                    Log.e("Ice Stream", "Error retrieving source URL. Error dialog message: "
                            + getString(callback.GetErrorStringID()));

                // Display the warning toast and/or error dialog
                getActivity().runOnUiThread(new Runnable() {
                    public void run() {
                        Activity activity = GetActivity();
                        if (callback.IsCancelled() == false && activity != null
                                && activity.isFinishing() == false) {
                            if (callback.GetWarningBoolean() == true) {
                                // Create and display a toast
                                Toast toast = Toast.makeText(getActivity(), callback.GetWarningStringID(),
                                        Toast.LENGTH_LONG);
                                toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 96);
                                toast.show();
                            }

                            if (callback.GetErrorBoolean() == true) {
                                // Create and display an error dialog
                                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                                builder.setMessage(callback.GetErrorStringID());
                                builder.setPositiveButton("OK", null);
                                builder.setCancelable(true);
                                builder.create().show();
                            }
                        }
                    }
                });

                if (callback.GetErrorBoolean() == true)
                    return null;
            }

            return url;
        }

        // Overridden method that runs after the onInBackground method
        @Override
        protected void onPostExecute(URL result) {
            // Dismiss the progress dialog
            mProgressDialog.dismiss();

            // Show a dialog with action choices
            if (result != null)
                ShowActionDialog(result);
        }
    }

    // Method called to get the activity
    // Returns null if an interrupt exception is encountered otherwise returns
    //    the activity, waiting for it if necessary during a screen rotation
    private Activity GetActivity() {
        Activity activity;
        while ((activity = getActivity()) == null) {
            try {
                Log.w("Ice Stream", "getActivity method returned null. Waiting 100ms before "
                        + "calling getActivity method again.");
                Thread.sleep(100);
            } catch (InterruptedException exception) {
                Log.d("Ice Stream", "Interrupted Exception encountered.", exception);
                return null;
            }
        }

        return activity;
    }

    // Method called to show the progress dialog
    private void ShowProgressDialog(final AsyncTask<?, ?, ?> task) {
        synchronized (mProgressDialogShowing) {
            // Save the passed task
            mProgressDialogTask = task;

            // Show the progress dialog
            mProgressDialog = ProgressDialog.show(getActivity(), "", getString(R.string.browse_progress), true,
                    true, new OnCancelListener() {
                        public void onCancel(DialogInterface dialog) {
                            synchronized (mProgressDialogShowing) {
                                // Cancel the task
                                task.cancel(true);

                                // Signal that the progress dialog will not longer be
                                //    showing
                                mProgressDialogShowing = false;
                            }
                        }
                    });
            mProgressDialogShowing = true;
        }
    }

    // Method called to restore the progress dialog
    private void RestoreProgressDialog() {
        synchronized (mProgressDialogShowing) {
            if (mProgressDialog != null && mProgressDialog.isShowing() == false && mProgressDialogTask != null
                    && mProgressDialogShowing == true)
                ShowProgressDialog(mProgressDialogTask);
        }
    }

    // Method called to dismiss the progress dialog
    private void DismissProgressDialog() {
        synchronized (mProgressDialogShowing) {
            if (mProgressDialog != null && mProgressDialog.isShowing() == true) {
                mProgressDialog.dismiss();
                mProgressDialogShowing = false;
            }
        }
    }

    // Method called to show the action dialog
    private void ShowActionDialog(final URL url) {
        // Save the passed URL
        mActionDialogURL = url;

        // Create and show the action dialog
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setItems(new String[] { "Open (Stream)", "Open (Download)", "Save" }, new OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                // Create the intent
                Intent intent = new Intent(getActivity(), OpenActivity.class);

                // Add the mode
                switch (item) {
                case 0:
                    intent.putExtra("Mode", OpenActivity.Mode.STREAM_OPEN.GetValue());
                    break;
                case 1:
                    intent.putExtra("Mode", OpenActivity.Mode.DOWNLOAD_OPEN.GetValue());
                    break;
                case 2:
                    intent.putExtra("Mode", OpenActivity.Mode.DOWNLOAD_SAVE.GetValue());
                    break;
                }

                // Add the link to the extras and open the activity
                intent.putExtra("Link", url);
                startActivity(intent);
            }
        });
        builder.setCancelable(true);
        mActionDialog = builder.create();
        mActionDialog.show();
    }

    // Method called to restore the action dialog
    private void RestoreActionDialog() {
        if (((mActionDialog != null && mActionDialog.isShowing() == false) || mActionDialog == null)
                && mActionDialogURL != null)
            ShowActionDialog(mActionDialogURL);
    }

    // The item array adapter class
    private class ItemArrayAdapter extends ArrayAdapter<Item> {
        // Variable
        List<Item> mItems;

        // Constructor
        public ItemArrayAdapter(Context context, int textViewResourceId, ArrayList<Item> objects) {
            // Call the base class constructor and store the passed info
            super(context, textViewResourceId, objects);
            mItems = objects;
        }

        // Overridden method called to create the view in the list
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return mItems.get(position).GetView(getActivity());
        }
    }

    // The heading list listener
    private OnItemClickListener mHeadingListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // Check which heading was clicked
            int i;
            for (i = 0; i < mHeading.length; ++i)
                if (parent == mHeading[i])
                    break;

            // Load the location if it's not already selected
            if (((HeadingItem) mHeadingItems[i].get(position)).IsSelected() == false)
                // TODO: The line below (or possibly above) received an error once - ArrayIndexOutOfBoundsException
                StartLoadStateTask(new State(Location.CreateLocationForItem(mHeadingItems[i].get(position))));
        }
    };

    // The list listener
    private OnItemClickListener mListListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // Open the source or load the location
            if (mListItems.get(position) instanceof SourceItem) {
                // Open the source location
                StartOpenSourceLocationTask(
                        (SourceLocation) Location.CreateLocationForItem(mListItems.get(position)));
            } else if (!(mListItems.get(position) instanceof InfoItem)) {
                // Open the location
                StartLoadStateTask(new State(Location.CreateLocationForItem(mListItems.get(position))));
            }
        }
    };

    // Method called when the back button is pressed
    public void OnBackPressed() {
        // Check if we should exit the activity
        if (mCurrentState.size() < 2) {
            getActivity().finish();
            return;
        }

        // Remove the last location from the current location list
        mCurrentState.removeLast();
        Log.d("Ice Stream", "Last state removed from current state list.");

        // Remove and open the now current location from the current location list
        StartLoadStateTask(mCurrentState.getLast());
        Log.d("Ice Stream", "New last state loaded as current state.");
    }

    // Class used to save information when pausing or saving the instance of the fragment
    private static class StopParcel implements Parcelable {
        // Variables
        private int[] mHeadingLayoutVisibility;
        private int mDividerVisibility;
        private int mRetryLayoutVisibility;
        private int mListScrollPosition;
        private int mListScrollOffset;
        private boolean mRestoreProgressDialog;
        private boolean mRestoreActionDialog;
        private URL mActionDialogURL;

        // Constructor
        protected StopParcel(BrowseFragment fragment) {
            // Create the heading layout visibility array
            mHeadingLayoutVisibility = new int[fragment.mHeadingLayout.length];

            // Save the heading layout visibility
            for (int i = 0; i < mHeadingLayoutVisibility.length; ++i)
                mHeadingLayoutVisibility[i] = fragment.mHeadingLayout[i].getVisibility();

            // Save the divider and retry layout visibility
            mDividerVisibility = fragment.mDivider.getVisibility();
            mRetryLayoutVisibility = fragment.mRetryLayout.getVisibility();

            // Save the list scroll position and offset
            mListScrollPosition = fragment.mList.getFirstVisiblePosition();
            View top = fragment.mList.getChildAt(0);
            mListScrollOffset = (top == null) ? 0 : top.getTop();

            // Save the progress dialog show state
            if (fragment.mProgressDialog != null && fragment.mProgressDialog.isShowing())
                mRestoreProgressDialog = true;
            else
                mRestoreProgressDialog = false;

            // Save the action dialog show state
            if (fragment.mActionDialog != null && fragment.mActionDialog.isShowing())
                mRestoreActionDialog = true;
            else
                mRestoreActionDialog = false;

            // Save the action dialog URL
            mActionDialogURL = fragment.mActionDialogURL;
        }

        // Constructor used by the parcelable functionality of this class
        protected StopParcel(Parcel in) {
            mHeadingLayoutVisibility = in.createIntArray();
            mDividerVisibility = in.readInt();
            mRetryLayoutVisibility = in.readInt();
            mListScrollPosition = in.readInt();
            mListScrollOffset = in.readInt();
            boolean[] array = new boolean[2];
            in.readBooleanArray(array);
            mRestoreProgressDialog = array[0];
            mRestoreActionDialog = array[1];
            String string = in.readString();
            if (string.length() > 0) {
                try {
                    mActionDialogURL = new URL(string);
                } catch (MalformedURLException exception) {
                    mActionDialogURL = null;
                }
            } else {
                mActionDialogURL = null;
            }
        }

        // Variable used by the parcelable functionality of this class
        @SuppressWarnings("unused")
        public static final Parcelable.Creator<StopParcel> CREATOR = new Parcelable.Creator<StopParcel>() {
            public StopParcel createFromParcel(Parcel in) {
                return new StopParcel(in);
            }

            public StopParcel[] newArray(int size) {
                return new StopParcel[size];
            }
        };

        // Method used by the parcelable functionality of this class
        public int describeContents() {
            return 0;
        }

        // Method used by the parcelable functionality of this class
        public void writeToParcel(Parcel out, int flags) {
            out.writeIntArray(mHeadingLayoutVisibility);
            out.writeInt(mDividerVisibility);
            out.writeInt(mRetryLayoutVisibility);
            out.writeInt(mListScrollPosition);
            out.writeInt(mListScrollOffset);
            out.writeBooleanArray(new boolean[] { mRestoreProgressDialog, mRestoreActionDialog });
            if (mActionDialogURL != null)
                out.writeString(mActionDialogURL.toString());
            else
                out.writeString("");
        }
    }

    // Overridden onCreate method
    @SuppressWarnings("unchecked")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Call base class method
        super.onCreate(savedInstanceState);

        // Create the heading layout and heading arrays
        mHeadingLayout = new LinearLayout[3];
        mHeading = new HorizontalListView[3];

        // Create the heading items and list items arrays
        mHeadingItems = new ArrayList[3];
        for (int i = 0; i < mHeadingItems.length; ++i)
            mHeadingItems[i] = new ArrayList<Item>();
        mListItems = new ArrayList<Item>();

        // Create the boolean object representing the progress dialog state
        mProgressDialogShowing = false;
    }

    // Overridden onCreateView method
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate our xml layout
        LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.browse_fragment, container, false);

        // Add the ad layout
        mAdLayout = new AdLayout(getActivity(), 30000, true);
        LinearLayout.LayoutParams adLayoutLP = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                (int) (50f * getResources().getDisplayMetrics().density + 0.5f));
        adLayoutLP.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
        layout.addView(mAdLayout, adLayoutLP);

        return layout;
    }

    // Overridden onActivityCreated method
    @SuppressWarnings("unchecked")
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        // Call base class method
        super.onActivityCreated(savedInstanceState);

        // Save the views
        mHeadingLayout[0] = (LinearLayout) getActivity().findViewById(R.id.heading1_layout);
        mHeading[0] = (HorizontalListView) getActivity().findViewById(R.id.heading1);
        mHeadingLayout[1] = (LinearLayout) getActivity().findViewById(R.id.heading2_layout);
        mHeading[1] = (HorizontalListView) getActivity().findViewById(R.id.heading2);
        mHeadingLayout[2] = (LinearLayout) getActivity().findViewById(R.id.heading3_layout);
        mHeading[2] = (HorizontalListView) getActivity().findViewById(R.id.heading3);
        mDivider = (ImageView) getActivity().findViewById(R.id.divider);
        mList = (ListView) getActivity().findViewById(R.id.list);
        mRetryLayout = (LinearLayout) getActivity().findViewById(R.id.retry_layout);
        mRetryButton = (Button) getActivity().findViewById(R.id.retry_button);

        // Create the array adapters
        mHeadingAdapter = new ItemArrayAdapter[3];
        for (int i = 0; i < mHeadingAdapter.length; ++i)
            mHeadingAdapter[i] = new ItemArrayAdapter(getActivity(), 0, mHeadingItems[i]);
        mListAdapter = new ItemArrayAdapter(getActivity(), 0, mListItems);

        // Set the list adapters
        for (int i = 0; i < mHeading.length; ++i)
            mHeading[i].setAdapter(mHeadingAdapter[i]);
        mList.setAdapter(mListAdapter);

        // Set the heading click listeners
        for (int i = 0; i < mHeading.length; ++i)
            mHeading[i].setOnItemClickListener(mHeadingListener);
        mList.setOnItemClickListener(mListListener);

        // Create or load certain items
        if (savedInstanceState == null && mStopParcel == null) {
            // Set the initial connection boolean and create the current location and
            //    list scroll position lists
            mInitialConnection = true;
            mCurrentState = new LinkedList<State>();
            Log.d("Ice Stream", "New current state list created.");
        } else if (savedInstanceState != null) {
            // Get the initial connection boolean and the current location list
            mInitialConnection = savedInstanceState.getBoolean("InitialConnection");
            mCurrentState = new LinkedList<State>((Collection<? extends State>) Arrays
                    .asList(savedInstanceState.getParcelableArray("CurrentState")));
            Log.d("Ice Stream", "Loaded current state list from saved instance state bundle.");
            Log.d("Ice Stream", "Current state list contains " + mCurrentState.size() + " state(s).");

            // Get the heading items lists
            for (int i = 0; i < mHeadingItems.length; ++i) {
                mHeadingItems[i].addAll((Collection<? extends Item>) savedInstanceState
                        .getParcelableArrayList("Heading" + (i + 1) + "Items"));
                Log.d("Ice Stream", "Loaded " + mHeadingItems[i].size() + " heading items "
                        + "from saved instance state bundle into heading " + (i + 1) + " items list.");
                mHeadingAdapter[i].notifyDataSetChanged();
            }

            // Get the list items list
            mListItems.addAll((Collection<? extends Item>) savedInstanceState.getParcelableArrayList("ListItems"));
            Log.d("Ice Stream", "Loaded " + mListItems.size() + " list items from saved instance "
                    + "state bundle into list items list.");
            mListAdapter.notifyDataSetChanged();

            // Get the stop parcel from the saved instance state bundle
            mStopParcel = savedInstanceState.getParcelable("StopParcel");
        }

        // Get the current state
        State state;
        if (mCurrentState.isEmpty()) {
            // Create a new location based on the passed URL
            URL url = (URL) getArguments().get("URL");
            state = new State(Location.CreateLocationForItem(new HeadingItem(url)));
        } else {
            state = mCurrentState.getLast();
        }

        // Load the state
        StartLoadStateTask(state);

        // Set the retry button click listener
        final State retryState = state;
        mRetryButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                StartLoadStateTask(retryState);
            }
        });
    }

    // Overridden onStart method
    @Override
    public void onStart() {
        // Call base class method
        super.onStart();

        if (mStopParcel != null) {
            // Restore the heading, divider, and retry button visibility
            for (int i = 0; i < mHeadingLayout.length; ++i)
                mHeadingLayout[i].setVisibility(mStopParcel.mHeadingLayoutVisibility[i]);
            mDivider.setVisibility(mStopParcel.mDividerVisibility);
            mRetryLayout.setVisibility(mStopParcel.mRetryLayoutVisibility);

            // Restore the list scroll position
            new Handler().postDelayed(new Runnable() {
                public void run() {
                    mList.setSelectionFromTop(mStopParcel.mListScrollPosition, mStopParcel.mListScrollOffset);
                }
            }, 100);

            // Restore the progress dialog
            if (mStopParcel.mRestoreProgressDialog)
                RestoreProgressDialog();

            // Restore the action dialog
            if (mStopParcel.mRestoreActionDialog) {
                mActionDialogURL = mStopParcel.mActionDialogURL;
                RestoreActionDialog();
            }
        }
    }

    // Overridden onResume method
    @Override
    public void onResume() {
        // Call base class method
        super.onResume();

        // Resume the ad layout refresh cycle
        if (mAdLayout != null)
            mAdLayout.Resume();
    }

    // Overridden onSaveInstanceState method
    @Override
    public void onSaveInstanceState(Bundle outState) {
        // Call base class method
        super.onSaveInstanceState(outState);

        // Save the stop parcel and other settings and lists
        outState.putParcelable("StopParcel", new StopParcel(this));
        outState.putBoolean("InitialConnection", mInitialConnection);
        outState.putParcelableArrayList("Heading1Items", mHeadingItems[0]);
        outState.putParcelableArrayList("Heading2Items", mHeadingItems[1]);
        outState.putParcelableArrayList("Heading3Items", mHeadingItems[2]);
        outState.putParcelableArrayList("ListItems", mListItems);
        outState.putParcelableArray("CurrentState", mCurrentState.toArray(new State[0]));
    }

    // Overridden onPause method
    public void onPause() {
        // Call base class method
        super.onPause();

        // Pause the ad layout refresh cycle
        if (mAdLayout != null)
            mAdLayout.Pause();
    }

    // Overridden onStop method
    @Override
    public void onStop() {
        // Call base class method
        super.onStop();

        // Create a stop parcel
        mStopParcel = new StopParcel(this);

        // Dismiss the progress dialog
        if (mProgressDialog != null && mProgressDialog.isShowing())
            mProgressDialog.dismiss();

        // Dismiss the action dialog
        if (mActionDialog != null && mActionDialog.isShowing())
            mActionDialog.dismiss();
    }

    // Overridden onDestroy method
    @Override
    public void onDestroy() {
        // Call base class onStop method
        super.onDestroy();

        // Cancel any running tasks
        if (mLoadStateTask != null && mLoadStateTask.getStatus() != AsyncTask.Status.FINISHED)
            mLoadStateTask.cancel(true);
        if (mOpenSourceLocationTask != null && mOpenSourceLocationTask.getStatus() != AsyncTask.Status.FINISHED)
            mOpenSourceLocationTask.cancel(true);

        // Cancel the ad layout refresh cycle
        if (mAdLayout != null)
            mAdLayout.Cancel();
    }
}