uk.org.rivernile.edinburghbustracker.android.fragments.general.NearestStopsFragment.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.rivernile.edinburghbustracker.android.fragments.general.NearestStopsFragment.java

Source

/*
 * Copyright (C) 2011 - 2013 Niall 'Rivernile' Scott
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors or contributors be held liable for
 * any damages arising from the use of this software.
 *
 * The aforementioned copyright holder(s) hereby grant you a
 * non-transferrable right to use this software for any purpose (including
 * commercial applications), and to modify it and redistribute it, subject to
 * the following conditions:
 *
 *  1. This notice may not be removed or altered from any file it appears in.
 *
 *  2. Any modifications made to this software, except those defined in
 *     clause 3 of this agreement, must be released under this license, and
 *     the source code of any modifications must be made available on a
 *     publically accessible (and locateable) website, or sent to the
 *     original author of this software.
 *
 *  3. Software modifications that do not alter the functionality of the
 *     software but are simply adaptations to a specific environment are
 *     exempt from clause 2.
 */

package uk.org.rivernile.edinburghbustracker.android.fragments.general;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.Html;
import android.text.Spanned;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import uk.org.rivernile.android.utils.GenericUtils;
import uk.org.rivernile.android.utils.LocationUtils;
import uk.org.rivernile.android.utils.SimpleResultLoader;
import uk.org.rivernile.edinburghbustracker.android.BusStopDatabase;
import uk.org.rivernile.edinburghbustracker.android.PreferencesActivity;
import uk.org.rivernile.edinburghbustracker.android.R;
import uk.org.rivernile.edinburghbustracker.android.SettingsDatabase;
import uk.org.rivernile.edinburghbustracker.android.fragments.dialogs.ServicesChooserDialogFragment;
import uk.org.rivernile.edinburghbustracker.android.fragments.dialogs.TurnOnGpsDialogFragment;

/**
 * Show a list of the nearest bus stops to the handset. If a location could not
 * be found or the user is too far away, an error message will be shown.
 * The user is able to filter the shown bus stops by what bus services stop
 * there. Long pressing on a bus stop shows a context menu where the user can
 * perform various actions on that stop. Tapping the stop shows bus times for
 * that stop.
 * 
 * @author Niall Scott
 */
public class NearestStopsFragment extends ListFragment
        implements LoaderManager.LoaderCallbacks<ArrayList<NearestStopsFragment.SearchResult>>, LocationListener,
        ServicesChooserDialogFragment.Callbacks {

    private static final String ARG_CHOSEN_SERVICES = "chosenServices";

    private static final int REQUEST_PERIOD = 10000;
    private static final float MIN_DISTANCE = 3.0f;

    private Callbacks callbacks;
    private LocationManager locMan;
    private SharedPreferences sp;
    private BusStopDatabase bsd;
    private SettingsDatabase sd;
    private Location lastLocation;
    private SearchResult selectedStop;

    private NearestStopsArrayAdapter ad;
    private String[] services;
    private String[] chosenServices;

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);

        try {
            callbacks = (Callbacks) activity;
        } catch (ClassCastException e) {
            throw new IllegalStateException(
                    activity.getClass().getName() + " does not implement " + Callbacks.class.getName());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Activity activity = getActivity();
        // Get references to required resources.
        locMan = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
        sp = activity.getSharedPreferences(PreferencesActivity.PREF_FILE, 0);
        bsd = BusStopDatabase.getInstance(activity.getApplicationContext());
        sd = SettingsDatabase.getInstance(activity.getApplicationContext());
        // Create the ArrayAdapter for the ListView.
        ad = new NearestStopsArrayAdapter(activity);

        // Initialise the services chooser Dialog.
        services = bsd.getBusServiceList();

        if (savedInstanceState != null) {
            chosenServices = savedInstanceState.getStringArray(ARG_CHOSEN_SERVICES);
        } else {
            // Check to see if GPS is enabled then check to see if the GPS
            // prompt dialog has been disabled.
            if (!locMan.isProviderEnabled(LocationManager.GPS_PROVIDER)
                    && !sp.getBoolean(PreferencesActivity.PREF_DISABLE_GPS_PROMPT, false)) {
                // Get the list of Activities which can handle the enabling of
                // location services.
                final List<ResolveInfo> packages = activity.getPackageManager()
                        .queryIntentActivities(TurnOnGpsDialogFragment.TURN_ON_GPS_INTENT, 0);
                // If the list is not empty, this means Activities do exist.
                // Show Dialog asking users if they want to turn on GPS.
                if (packages != null && !packages.isEmpty()) {
                    callbacks.onAskTurnOnGps();
                }
            }
        }

        // Tell the underlying Activity that this Fragment contains an options
        // menu.
        setHasOptionsMenu(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
            final Bundle savedInstanceState) {
        return inflater.inflate(R.layout.neareststops, container, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onActivityCreated(final Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // The ListView items can show a context menu.
        registerForContextMenu(getListView());
        // Set the ListView adapter.
        setListAdapter(ad);

        // Initialise the lastLocation to the best known location.
        lastLocation = LocationUtils.getBestInitialLocation(locMan);

        // Force an update to initially show data.
        doUpdate(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onResume() {
        super.onResume();

        // Start the location providers if they are enabled.
        if (locMan.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
            locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, REQUEST_PERIOD, MIN_DISTANCE, this);
        }

        if (locMan.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, REQUEST_PERIOD, MIN_DISTANCE, this);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPause() {
        super.onPause();

        // When the Activity is being paused, cancel location updates.
        locMan.removeUpdates(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onSaveInstanceState(final Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putStringArray(ARG_CHOSEN_SERVICES, chosenServices);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
        // Inflate the menu.
        inflater.inflate(R.menu.neareststops_option_menu, menu);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPrepareOptionsMenu(final Menu menu) {
        super.onPrepareOptionsMenu(menu);

        final MenuItem item = menu.findItem(R.id.neareststops_option_menu_filter);
        item.setEnabled(services != null && services.length > 0);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
        case R.id.neareststops_option_menu_filter:
            callbacks.onShowServicesChooser(services, chosenServices,
                    getString(R.string.neareststops_service_chooser_title));
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);

        // Get the MenuInflater.
        final MenuInflater inflater = getActivity().getMenuInflater();
        // Cast the menuInfo object to something we understand.
        final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
        // Get the item relating to the selected item.
        selectedStop = ad.getItem(info.position);

        final String name;

        if (selectedStop.locality != null) {
            name = getString(R.string.busstop_locality, selectedStop.stopName, selectedStop.locality,
                    selectedStop.stopCode);
        } else {
            name = getString(R.string.busstop, selectedStop.stopName, selectedStop.stopCode);
        }

        // Set the title of the context menu.
        menu.setHeaderTitle(name);

        // Inflate the menu from XML.
        inflater.inflate(R.menu.neareststops_context_menu, menu);

        // Title depends on whether it's already a favourite or not.
        MenuItem item = menu.findItem(R.id.neareststops_context_menu_favourite);
        if (sd.getFavouriteStopExists(selectedStop.stopCode)) {
            item.setTitle(R.string.neareststops_context_remasfav);
        } else {
            item.setTitle(R.string.neareststops_context_addasfav);
        }

        // Title depends on whether a proximity alert has already been added or
        // not.
        item = menu.findItem(R.id.neareststops_context_menu_prox_alert);
        if (sd.isActiveProximityAlert(selectedStop.stopCode)) {
            item.setTitle(R.string.neareststops_menu_prox_rem);
        } else {
            item.setTitle(R.string.neareststops_menu_prox_add);
        }

        // Title depends on whether a time alert has already been added or not.
        item = menu.findItem(R.id.neareststops_context_menu_time_alert);
        if (sd.isActiveTimeAlert(selectedStop.stopCode)) {
            item.setTitle(R.string.neareststops_menu_time_rem);
        } else {
            item.setTitle(R.string.neareststops_menu_time_add);
        }

        // If the Google Play Services is not available, then don't show the
        // option to show the stop on the map.
        item = menu.findItem(R.id.neareststops_context_menu_showonmap);

        if (!GenericUtils.isGoogleMapsAvailable(getActivity())) {
            item.setVisible(false);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onContextItemSelected(final MenuItem item) {
        // Make sure that the selectedStop exists.
        if (selectedStop == null)
            return false;

        switch (item.getItemId()) {
        case R.id.neareststops_context_menu_favourite:
            // See if this stop exists as a favourite already.
            if (sd.getFavouriteStopExists(selectedStop.stopCode)) {
                callbacks.onShowConfirmFavouriteDeletion(selectedStop.stopCode);
            } else {
                // If it doesn't exist, show the Add Favourite Stop
                // interface.
                callbacks.onShowAddFavouriteStop(selectedStop.stopCode,
                        selectedStop.locality != null ? selectedStop.stopName + ", " + selectedStop.locality
                                : selectedStop.stopName);
            }

            return true;
        case R.id.neareststops_context_menu_prox_alert:
            // See if this stop exists as a proximity alert.
            if (sd.isActiveProximityAlert(selectedStop.stopCode)) {
                callbacks.onShowConfirmDeleteProximityAlert();
            } else {
                callbacks.onShowAddProximityAlert(selectedStop.stopCode);
            }

            return true;
        case R.id.neareststops_context_menu_time_alert:
            // See if this stop exists as a time alert.
            if (sd.isActiveTimeAlert(selectedStop.stopCode)) {
                callbacks.onShowConfirmDeleteTimeAlert();
            } else {
                callbacks.onShowAddTimeAlert(selectedStop.stopCode);
            }

            return true;
        case R.id.neareststops_context_menu_showonmap:
            // Start the BusStopMapActivity, giving it a stopCode.
            callbacks.onShowBusStopMapWithStopCode(selectedStop.stopCode);

            return true;
        default:
            return super.onContextItemSelected(item);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Loader<ArrayList<SearchResult>> onCreateLoader(final int id, final Bundle args) {
        // Create a new Loader.
        return new NearestStopsLoader(getActivity(), args);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLoadFinished(final Loader<ArrayList<SearchResult>> loader,
            final ArrayList<SearchResult> results) {
        if (isAdded()) {
            final ListView lv = getListView();
            // Get the first visible position so the scroll position is restored
            // later.
            int currentIndex = lv.getFirstVisiblePosition();
            final View v = lv.getChildAt(0);

            // When loading has finished, clear the ArrayAdapter and add in all
            // the new results.
            ad.clear();
            ad.addAll(results);

            final int lastIndex = results.size() - 1;

            if (lastIndex < currentIndex) {
                // If the final index is less than the index before, then scroll
                // to the new final index.
                lv.setSelectionFromTop(lastIndex, 0);
            } else {
                // Otherwise, go to the previous exact scroll position.
                lv.setSelectionFromTop(currentIndex, (v == null) ? 0 : v.getTop());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLoaderReset(final Loader<ArrayList<SearchResult>> loader) {
        // If the Loader has been reset, clear the SearchResults.
        ad.clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLocationChanged(final Location location) {
        // When the location has changed, cache the new location and force an
        // update.
        if (LocationUtils.isBetterLocation(location, lastLocation)) {
            lastLocation = location;
            doUpdate(false);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onStatusChanged(final String provider, final int status, final Bundle extras) {
        // Nothing to do here.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onProviderEnabled(final String provider) {
        if (LocationManager.GPS_PROVIDER.equals(provider) || LocationManager.NETWORK_PROVIDER.equals(provider)) {
            locMan.requestLocationUpdates(provider, REQUEST_PERIOD, MIN_DISTANCE, this);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onProviderDisabled(final String provider) {
        // Nothing to do here.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onServicesChosen(final String[] chosenServices) {
        this.chosenServices = chosenServices;

        // The user has been in the services chooser Dialog, so force an update
        // incase anything has changed.
        doUpdate(false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onListItemClick(final ListView l, final View v, final int position, final long id) {
        // Ensure that the position is within range.
        if (position < ad.getCount()) {
            // Show the DisplayStopDataActivity.
            callbacks.onShowBusTimes(ad.getItem(position).stopCode);
        }
    }

    /**
     * Cause the data to refresh. The refresh happens asynchronously in another
     * thread.
     * 
     * @param isFirst Is this the first load?
     */
    private void doUpdate(final boolean isFirst) {
        if (lastLocation == null)
            return;

        // Stuff the arguments Bundle.
        final Bundle args = new Bundle();
        args.putDouble(NearestStopsLoader.ARG_LATITUDE, lastLocation.getLatitude());
        args.putDouble(NearestStopsLoader.ARG_LONGITUDE, lastLocation.getLongitude());

        // Only put this argument in if chosen services exist.
        if (chosenServices != null && chosenServices.length > 0)
            args.putStringArray(NearestStopsLoader.ARG_FILTERED_SERVICES, chosenServices);

        if (isFirst) {
            getLoaderManager().initLoader(0, args, this);
        } else {
            getLoaderManager().restartLoader(0, args, this);
        }
    }

    /**
     * A SearchResult is essentially bean object to hold the values returned for
     * the database for a particular bus stop. The fields are intentially public
     * for quick execution speed within this class.
     */
    public static class SearchResult implements Comparable<SearchResult> {

        public String stopCode;
        public String stopName;
        public Spanned services;
        public float distance;
        public int orientation;
        public String locality;

        /**
         * Create a new SearchResult instance, specifying default values.
         * 
         * @param stopCode The bus stop code.
         * @param stopName The bus stop name.
         * @param services A String denoting which services stop at this bus
         * stop.
         * @param distance The distance from the handset to this bus stop.
         * @param point The location of this bus stop.
         * @param orientation The direction the bus stop faces.
         * @param locality The locality for this bus stop.
         */
        public SearchResult(final String stopCode, final String stopName, final Spanned services,
                final float distance, final int orientation, final String locality) {
            this.stopCode = stopCode;
            this.stopName = stopName;
            this.services = services;
            this.distance = distance;
            this.orientation = orientation;
            this.locality = locality;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int compareTo(final SearchResult item) {
            if (distance == item.distance)
                return 0;

            // Sort the distance in ascending order.
            return distance > item.distance ? 1 : -1;
        }
    }

    /**
     * The NearestStopsLoader accepts an argument Bundle which contains the
     * handset\'s current latitude and longitude and optionally a list of
     * filtered bus services. It will then query the bus stop database to result
     * a list of matching results.
     */
    private static class NearestStopsLoader extends SimpleResultLoader<ArrayList<SearchResult>> {

        /** The latitude argument. */
        public static final String ARG_LATITUDE = "latitude";
        /** The longitude argument. */
        public static final String ARG_LONGITUDE = "longitude";
        /** The filtered services argument. */
        public static final String ARG_FILTERED_SERVICES = "filteredServices";

        /** If modifying for another city, check that this value is correct. */
        private static final double LATITUDE_SPAN = 0.004499;
        /** If modifying for another city, check that this value is correct. */
        private static final double LONGITUDE_SPAN = 0.008001;

        private final BusStopDatabase bsd;
        private final Bundle args;

        /**
         * Create a new instance of the NearestStopsLoader. An argument Bundle
         * MUST be supplied.
         * 
         * @param context A Context object.
         * @param args The argument Bundle.
         */
        public NearestStopsLoader(final Context context, final Bundle args) {
            super(context);

            // Make sure the argument Bundle exists.
            if (args == null)
                throw new IllegalArgumentException("The args cannot be null.");

            // Set the class fields.
            bsd = BusStopDatabase.getInstance(context.getApplicationContext());
            this.args = args;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public ArrayList<SearchResult> loadInBackground() {
            // Create the List where the results will be placed. If no results
            // have been found, this list will be empty.
            final ArrayList<SearchResult> result = new ArrayList<SearchResult>();

            // Cannot continue without coordinates. Return the empty list.
            if (!args.containsKey(ARG_LATITUDE) || !args.containsKey(ARG_LONGITUDE)) {
                return result;
            }

            // Get the latitude and longitude arguments.
            final double latitude = args.getDouble(ARG_LATITUDE);
            final double longitude = args.getDouble(ARG_LONGITUDE);

            // Calculate the bounds.
            final double minX = latitude - LATITUDE_SPAN;
            final double minY = longitude - LONGITUDE_SPAN;
            final double maxX = latitude + LATITUDE_SPAN;
            final double maxY = longitude + LONGITUDE_SPAN;

            // Do not let anything else touch the stop database while we are
            // querying it.
            synchronized (bsd) {
                Cursor c;
                // What query is executed depends on whether services are being
                // filtered or not.
                if (args.containsKey(ARG_FILTERED_SERVICES)) {
                    c = bsd.getFilteredStopsByCoords(minX, minY, maxX, maxY,
                            args.getStringArray(ARG_FILTERED_SERVICES));
                } else {
                    c = bsd.getBusStopsByCoords(minX, minY, maxX, maxY);
                }

                // Defensive programming!
                if (c != null) {
                    // We don't care about the bearings so a float array of only
                    // 1 in size is required.
                    final float[] distance = new float[1];
                    distance[0] = 0f;

                    // Loop through all results.
                    while (c.moveToNext()) {
                        // Use the Location class in the Android framework to
                        // compute the distance between the handset and the bus
                        // stop.
                        Location.distanceBetween(latitude, longitude, c.getDouble(2), c.getDouble(3), distance);
                        final String stopCode = c.getString(0);
                        // Create a new SearchResult and add it to the results
                        // list.
                        result.add(new SearchResult(stopCode, c.getString(1),
                                BusStopDatabase
                                        .getColouredServiceListString(bsd.getBusServicesForStopAsString(stopCode)),
                                distance[0], c.getInt(4), c.getString(5)));
                    }

                    // Cursor is no longer needed, free the resource.
                    c.close();
                }
            }

            // Sort the bus stop results in order of distance from the handset.
            Collections.sort(result);

            return result;
        }
    }

    /**
     * This ArrayAdapter holds a collection of SearchResult objects which
     * describe each bus stop to show. This class is necessary because a custom
     * layout is required for each row item.
     */
    private static class NearestStopsArrayAdapter extends ArrayAdapter<SearchResult> {

        private LayoutInflater vi;

        /**
         * Create a new NearestStopsArrayAdapter.
         * 
         * @param context A Context object.
         */
        public NearestStopsArrayAdapter(final Context context) {
            super(context, R.layout.neareststops_list_item, android.R.id.text1);

            // Get a LayoutInflater.
            vi = LayoutInflater.from(context);
        }

        /**
         * This is a convenience method which adds all the items from an
         * ArrayList to this adapter. It calls through to the add() method in
         * the parent class.
         * 
         * @param collection The collection to add to the end of this adapter.
         */
        public void addAll(final ArrayList<SearchResult> collection) {
            for (SearchResult sr : collection) {
                add(sr);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public View getView(final int position, final View convertView, final ViewGroup parent) {
            final View row;
            // If a previous row exists, use that so that XML doesn't need to
            // be inflated.
            if (convertView != null) {
                row = convertView;
            } else {
                row = vi.inflate(R.layout.neareststops_list_item, null);
            }

            // Get the TextView objects.
            final TextView distance = (TextView) row.findViewById(R.id.txtNearestDistance);
            final TextView stopDetails = (TextView) row.findViewById(android.R.id.text1);
            final TextView buses = (TextView) row.findViewById(android.R.id.text2);
            // Get the SearchResult at position.
            final SearchResult sr = getItem(position);
            // Set the distance text.
            distance.setText((int) sr.distance + " m");

            final String name;

            if (sr.locality != null) {
                name = getContext().getString(R.string.busstop_locality_coloured, sr.stopName, sr.locality,
                        sr.stopCode);
            } else {
                name = getContext().getString(R.string.busstop_coloured, sr.stopName, sr.stopCode);
            }

            stopDetails.setText(Html.fromHtml(name));

            buses.setText(sr.services);

            // Set the bus stop marker icon.
            switch (sr.orientation) {
            case 0:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_n, 0, 0);
                break;
            case 1:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_ne, 0, 0);
                break;
            case 2:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_e, 0, 0);
                break;
            case 3:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_se, 0, 0);
                break;
            case 4:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_s, 0, 0);
                break;
            case 5:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_sw, 0, 0);
                break;
            case 6:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_w, 0, 0);
                break;
            case 7:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker_nw, 0, 0);
                break;
            default:
                distance.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.mapmarker, 0, 0);
                break;
            }

            return row;
        }
    }

    /**
     * Any Activities which host this Fragment must implement this interface to
     * handle navigation events.
     */
    public static interface Callbacks {

        /**
         * This is called when the user should be asked if they want to turn on
         * GPS or not.
         */
        public void onAskTurnOnGps();

        /**
         * This is called when it should be confirmed with the user that they
         * want to delete a favourite bus stop.
         * 
         * @param stopCode The bus stop that the user may want to delete.
         */
        public void onShowConfirmFavouriteDeletion(String stopCode);

        /**
         * This is called when it should be confirmed with the user that they
         * want to delete the proximity alert.
         */
        public void onShowConfirmDeleteProximityAlert();

        /**
         * This is called when it should be confirmed with the user that they
         * want to delete the time alert.
         */
        public void onShowConfirmDeleteTimeAlert();

        /**
         * This is called when the user wishes to select services, for example,
         * for filtering.
         * 
         * @param services The services to choose from.
         * @param selectedServices Any services that should be selected by
         * default.
         * @param title A title to show on the chooser.
         */
        public void onShowServicesChooser(String[] services, String[] selectedServices, String title);

        /**
         * This is called when the user wants to add a new favourite bus stop.
         * 
         * @param stopCode The stop code of the bus stop to add.
         * @param stopName The default name to use for the bus stop.
         */
        public void onShowAddFavouriteStop(String stopCode, String stopName);

        /**
         * This is called when the user wants to view the interface to add a new
         * proximity alert.
         * 
         * @param stopCode The stopCode the proximity alert should be added for.
         */
        public void onShowAddProximityAlert(String stopCode);

        /**
         * This is called when the user wants to view the interface to add a new
         * time alert.
         * 
         * @param stopCode The stopCode the time alert should be added for.
         */
        public void onShowAddTimeAlert(String stopCode);

        /**
         * This is called when the user wants to view the bus stop map centered
         * on a specific bus stop.
         * 
         * @param stopCode The stopCode that the map should center on.
         */
        public void onShowBusStopMapWithStopCode(String stopCode);

        /**
         * This is called when the user wishes to view bus stop times.
         * 
         * @param stopCode The bus stop to view times for.
         */
        public void onShowBusTimes(String stopCode);
    }
}