com.andybotting.tubechaser.activity.StationDetail.java Source code

Java tutorial

Introduction

Here is the source code for com.andybotting.tubechaser.activity.StationDetail.java

Source

/*  
 * Copyright 2010 Andy Botting <andy@andybotting.com>  
 *  
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This file 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  
 * General Public License for more details.  
 *  
 * You should have received a copy of the GNU General Public License  
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.  
 *  
 * This file incorporates work covered by the following copyright and  
 * permission notice:
 * 
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.andybotting.tubechaser.activity;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import com.andybotting.tubechaser.R;
import com.andybotting.tubechaser.objects.DepartureBoard;
import com.andybotting.tubechaser.objects.NextDeparture;
import com.andybotting.tubechaser.objects.Line;
import com.andybotting.tubechaser.objects.Platform;
import com.andybotting.tubechaser.objects.Station;
import com.andybotting.tubechaser.provider.DLRDepartures;
import com.andybotting.tubechaser.provider.NatRailDepartures;
import com.andybotting.tubechaser.provider.ServiceDepartures;
import com.andybotting.tubechaser.provider.TfLTubeDepartures;
import com.andybotting.tubechaser.provider.TubeChaserProvider;
import com.andybotting.tubechaser.provider.TubeChaserProviderException;
import com.andybotting.tubechaser.provider.TubeChaserContract.Stations;
import com.andybotting.tubechaser.utils.GenericUtil;
import com.andybotting.tubechaser.utils.PreferenceHelper;
import com.andybotting.tubechaser.utils.UIUtils;

import android.app.AlertDialog;
import android.app.ExpandableListActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.BaseExpandableListAdapter;
import android.widget.CompoundButton;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.TextView;

public class StationDetail extends ExpandableListActivity {
    private static final String TAG = "StationDetail";
    private static final boolean LOGV = Log.isLoggable(TAG, Log.INFO);

    public static final String EXTRA_LINE = "extra_line";

    // Menu items
    private static final int MENU_REFRESH = 0;
    private static final int MENU_STAR = 1;
    private static final int MENU_MAP = 2;

    private PreferenceHelper mPreferenceHelper;
    private TubeChaserProvider mProvider;
    private Station mStation;
    private Line mLine;
    private Context mContext;

    private ExpandableListAdapter mListAdapter;
    private ExpandableListView mListView;
    private CompoundButton mStarredButton;

    private DepartureBoard mDepartureBoard;

    private String mErrorMessage;
    private int mErrorRetry = 0;

    // Maximum errors before failing and showing dialog
    private final int MAX_ERRORS = 3;

    private boolean mFirstDepartureReqest = true;
    private Uri mLineUri;
    private boolean mStarred;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Uri stationUri = getIntent().getData();

        mContext = this.getBaseContext();
        mPreferenceHelper = new PreferenceHelper();
        mProvider = new TubeChaserProvider();

        mStation = mProvider.getStation(mContext, stationUri);
        mDepartureBoard = new DepartureBoard(); // just so it's not null

        // Get the line, if given in last activity
        mLineUri = getIntent().getParcelableExtra(EXTRA_LINE);

        // No line was passed in previous activity
        if (mLineUri == null) {

            // Get lines list
            Uri linesUri = Stations.buildLinesUri(mStation.getUri());
            final List<Line> lines = mProvider.getLines(mContext, linesUri);

            // If we only find one line, just use it
            if (lines.size() == 1) {
                mLine = lines.get(0);
                displayStation();
            } else {
                if (LOGV)
                    Log.v(TAG, "Showing select dialog");
                // Two or more lines, so show selecter
                showLineSelect(lines);
            }
        } else {
            displayStation();
        }

    }

    /**
     * Show line select dialog
     */
    private void showLineSelect(final List<Line> lines) {

        ListAdapter adapter = new LinesListAdapter(mContext, -1, lines);

        final AlertDialog.Builder ad = new AlertDialog.Builder(this);
        ad.setTitle("Select Line");

        ad.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                mLine = lines.get(which);
                dialog.dismiss();
                displayStation();
            }
        });

        ad.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                // Just end our activity if no line is selected
                StationDetail.this.finish();
            }
        });

        ad.create();
        ad.show();
    }

    /**
     * Display station
     */
    private void displayStation() {

        setContentView(R.layout.activity_station_detail);

        // Home button
        findViewById(R.id.btn_title_home).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                UIUtils.goHome(StationDetail.this);
            }
        });

        // Refresh button
        findViewById(R.id.btn_title_refresh).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                new GetDepartures().execute();
            }
        });

        // Map button
        findViewById(R.id.btn_title_map).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                final Intent intent = new Intent(StationDetail.this, StationMap.class);
                intent.putExtra(StationMap.EXTRA_STATION, mStation.getUri());
                startActivityForResult(intent, 1);
            }
        });

        // Set up our list
        mListAdapter = new DepartureListAdapter();

        mListView = getExpandableListView();
        Display display = getWindowManager().getDefaultDisplay();
        mListView.setIndicatorBounds(display.getWidth() - 50, display.getWidth() - 10);

        // Get our line, if we haven't already
        if (mLine == null)
            mLine = mProvider.getLine(mContext, mLineUri);

        // Set the title bar text
        ((TextView) findViewById(R.id.title_text)).setText(mLine.getLineName());

        // Set the title bar colour
        String lineColour = mLine.getColour();
        int colour = Color.parseColor("#" + lineColour);
        UIUtils.setTitleBarColor(findViewById(R.id.title_container), colour);

        // Set the station text
        ((TextView) findViewById(R.id.station_name)).setText(mStation.getName());
        ((TextView) findViewById(R.id.station_lines)).setText(mLine.getLineName());

        // Get Favourite
        mStarred = mPreferenceHelper.isStarred(mStation.getId(), mLine.getId());

        // Star button
        mStarredButton = (CompoundButton) findViewById(R.id.star_button);
        mStarredButton.setChecked(mStarred);

        // Star button OnClick
        mStarredButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                toggleStarred();
            }
        });

        new GetDepartures().execute();
    }

    /**
     * Set the empty text view text
     * @param text
     * @return
     */
    private void setEmptyText(String text) {
        // Line Code is null so set the empty view text to
        // a nice friendly message
        TextView emptyText = (TextView) findViewById(R.id.empty_text);
        if (emptyText != null)
            emptyText.setText(text);
    }

    /**
     * Toggle the station 'starred' status 
     */
    public void toggleStarred() {
        if (mStarred)
            mPreferenceHelper.unstarStation(mStation.getId(), mLine.getId());
        else
            mPreferenceHelper.starStation(mStation.getId(), mLine.getId());

        mStarred = mStarred ? false : true;
        mStarredButton.setChecked(mStarred);
    }

    /**
     * Create the options menu
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        menu.add(0, MENU_REFRESH, 0, R.string.menu_item_refresh).setIcon(R.drawable.ic_menu_refresh);

        menu.add(0, MENU_STAR, 0, "") // Set in onMenuOpened
                .setIcon(R.drawable.ic_menu_star);

        menu.add(0, MENU_MAP, 0, R.string.menu_item_map).setIcon(R.drawable.ic_menu_mapmode);

        return true;
    }

    /**
     * Menu opened
     */
    @Override
    public boolean onMenuOpened(int featureId, Menu menu) {
        // Ensure the 'Favourite' menu item has the correct text
        menu.getItem(MENU_STAR).setTitle((mStarred ? "Unfavourite" : "Favourite"));
        return super.onMenuOpened(featureId, menu);
    }

    /**
     * Options item selected
     */
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
        case MENU_REFRESH:
            // Refresh
            new GetDepartures().execute();
            return true;
        case MENU_STAR:
            // Star/Favourite
            toggleStarred();
            return true;
        case MENU_MAP:
            // Map view
            final Intent intent = new Intent(this, StationMap.class);
            intent.putExtra(StationMap.EXTRA_STATION, mStation.getUri());
            startActivityForResult(intent, 1);
            return true;
        }
        return false;
    }

    /**
     * Update the departures list
     */
    public void updateDeparturesList() {
        updateRefreshStatus(false);
        setListAdapter(mListAdapter);

        // Expand groups
        for (int i = 0; i < mListAdapter.getGroupCount(); i++)
            mListView.expandGroup(i);
    }

    /**
     * Update refresh status icon/views
     */
    private void updateRefreshStatus(boolean isRefreshing) {

        if (mListAdapter.getGroupCount() == 0)
            mListView.getEmptyView().setVisibility(isRefreshing ? View.GONE : View.VISIBLE);
        else
            mListView.setVisibility(isRefreshing ? View.GONE : View.VISIBLE);

        mListView.setVisibility(isRefreshing ? View.GONE : View.VISIBLE);
        findViewById(R.id.departures_loading).setVisibility(isRefreshing ? View.VISIBLE : View.GONE);

        findViewById(R.id.btn_title_refresh).setVisibility(isRefreshing ? View.GONE : View.VISIBLE);
        findViewById(R.id.title_refresh_progress).setVisibility(isRefreshing ? View.VISIBLE : View.GONE);
    }

    /**
     * Return the correct class for departure information for this line
     * @return departure service
     */
    private ServiceDepartures getDepartureService() {

        ServiceDepartures departures = null;

        switch (mLine.getType()) {
        case Line.TYPE_TUBE:
            departures = new TfLTubeDepartures();
            departures.setLine(mLine.getCode());
            break;
        case Line.TYPE_DLR:
            departures = new DLRDepartures();
            break;
        case Line.TYPE_OVERGROUND:
        case Line.TYPE_NATIONAL_RAIL:
            PreferenceHelper preferenceHelper = new PreferenceHelper();
            if (preferenceHelper.isNationalRailAPIEnabled()) {
                departures = new NatRailDepartures();
            }
            break;
        }
        return departures;
    }

    /**
     * Get the departures as a background task 
     */
    private class GetDepartures extends AsyncTask<DepartureBoard, Void, DepartureBoard> {

        @Override
        protected void onPreExecute() {
            // Show the loading spinners
            updateRefreshStatus(true);
        }

        @Override
        protected DepartureBoard doInBackground(final DepartureBoard... params) {
            try {
                Uri lineStationCodeUri = Stations.buildCodeUri(mStation.getId(), mLine.getId());
                String lineStationCode = mProvider.getLineStationCode(mContext, lineStationCodeUri);

                String errorMessage = null;
                ServiceDepartures departures = getDepartureService();

                // Departure information not available for DLR
                if (departures == null)
                    errorMessage = "Departure information is not available for the " + mLine.getLineName();

                // lineStationCode is non-existant, so we don't support it
                if (lineStationCode == null)
                    errorMessage = "Departure information is not available for " + mStation.getName();

                // Some error in this process, so we show it
                if (errorMessage != null)
                    setEmptyText(errorMessage);
                else
                    mDepartureBoard = departures.getNextDepartures(lineStationCode);

            } catch (TubeChaserProviderException e) {
                // Retry a couple of times before error
                if (mErrorRetry < MAX_ERRORS) {
                    mErrorRetry++;
                    if (LOGV)
                        Log.v(TAG, "Error " + mErrorRetry + " of " + MAX_ERRORS + ": " + e);
                    this.doInBackground(params);
                } else {
                    // Save the error message for the toast
                    mErrorMessage = e.getMessage();
                    e.printStackTrace();
                }
            }
            return mDepartureBoard;
        }

        @Override
        protected void onPostExecute(DepartureBoard mDepartureBoard) {
            if (mErrorRetry == MAX_ERRORS) {
                // Toast: Unable to download departure information
                UIUtils.popToast(mContext, R.string.dialog_error_downloading + " (" + mErrorMessage + ")");
                mErrorMessage = null;
                mErrorRetry = 0;
            } else {

                List<Platform> platforms = mDepartureBoard.getPlatforms();
                int numberOfDeparturesToShow = mPreferenceHelper.numberOfDepartures();

                for (Platform platform : platforms) {
                    List<NextDeparture> nextDepartures = platform.getNextDepartures();
                    Collections.sort(nextDepartures);
                    while (nextDepartures.size() > numberOfDeparturesToShow) {
                        nextDepartures.remove(nextDepartures.size() - 1);
                    }
                }

                updateDeparturesList();

                // Upload stats
                if (mFirstDepartureReqest) {
                    if (mPreferenceHelper.isSendStatsEnabled()) {
                        new Thread() {
                            public void run() {
                                uploadStats();
                            }
                        }.start();
                    }
                    mFirstDepartureReqest = false;
                }

            }

            // Hide the loading spinners
            updateRefreshStatus(false);
        }
    }

    public class LinesListAdapter extends ArrayAdapter {

        List<Line> lines;

        public LinesListAdapter(Context context, int textViewResourceId, List<Line> lines) {
            super(context, textViewResourceId, lines);
            this.lines = lines;
        }

        public int getCount() {
            return lines.size();
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            View pv;
            if (convertView == null) {
                LayoutInflater inflater = getLayoutInflater();
                pv = inflater.inflate(R.layout.lines_select_row, parent, false);
            } else {
                pv = convertView;
            }

            Line line = (Line) lines.get(position);

            ((TextView) pv.findViewById(R.id.line_name)).setText(line.getLineName());

            // Parse our 6-char hex value into an int for android
            int colour = Color.parseColor("#" + line.getColour());
            ((View) pv.findViewById(R.id.line_colour)).setBackgroundColor(colour);

            return pv;

        }
    }

    /**
     * Departures list adapter
     */
    public class DepartureListAdapter extends BaseExpandableListAdapter {

        public Object getChild(int groupPosition, int childPosition) {
            return mDepartureBoard.getPlatform(groupPosition).getDeparture(childPosition);
        }

        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        public int getChildrenCount(int groupPosition) {
            return mDepartureBoard.getPlatform(groupPosition).numberOfDepartures();
        }

        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
                ViewGroup parent) {
            View pv;
            if (convertView == null) {
                LayoutInflater inflater = getLayoutInflater();
                pv = inflater.inflate(R.layout.departures_row, parent, false);
            } else {
                pv = convertView;
            }

            NextDeparture nextDeparture = (NextDeparture) mDepartureBoard.getPlatform(groupPosition)
                    .getDeparture(childPosition);

            ((TextView) pv.findViewById(R.id.departure_destination)).setText(nextDeparture.getDestination());
            ((TextView) pv.findViewById(R.id.departure_location)).setText(nextDeparture.getLocation());
            ((TextView) pv.findViewById(R.id.departure_time)).setText(nextDeparture.humanMinutesAway());

            return pv;
        }

        public Object getGroup(int groupPosition) {
            return mDepartureBoard.getPlatform(groupPosition);
        }

        public int getGroupCount() {
            return mDepartureBoard.numberOfPlatforms();
        }

        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            View pv;
            if (convertView == null) {
                LayoutInflater inflater = getLayoutInflater();
                pv = inflater.inflate(R.layout.departures_row_header, parent, false);
            } else {
                pv = convertView;
            }
            ((TextView) pv.findViewById(R.id.departure_platform)).setText(getGroup(groupPosition).toString());
            return pv;
        }

        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return true;
        }

        public boolean hasStableIds() {
            return true;
        }

    }

    /**
     * Upload stats
     */
    private void uploadStats() {
        if (LOGV)
            Log.v(TAG, "Sending Station/Line statistics");

        // gather all of the device info
        try {
            TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
            String device_uuid = tm.getDeviceId();
            String device_id = "00000000000000000000000000000000";
            if (device_uuid != null) {
                device_id = GenericUtil.MD5(device_uuid);
            }

            LocationManager mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
            Location location = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

            // post the data
            HttpClient client = new DefaultHttpClient();
            HttpPost post = new HttpPost("http://tubechaser.andybotting.com/stats/depart/send");
            post.setHeader("Content-Type", "application/x-www-form-urlencoded");

            List<NameValuePair> pairs = new ArrayList<NameValuePair>();

            pairs.add(new BasicNameValuePair("device_id", device_id));
            pairs.add(new BasicNameValuePair("station_id", String.valueOf(mStation.getId())));
            pairs.add(new BasicNameValuePair("line_id", String.valueOf(mLine.getId())));

            if (location != null) {
                pairs.add(new BasicNameValuePair("latitude", String.valueOf(location.getLatitude())));
                pairs.add(new BasicNameValuePair("longitude", String.valueOf(location.getLongitude())));
                pairs.add(new BasicNameValuePair("accuracy", String.valueOf(location.getAccuracy())));
            }

            try {
                post.setEntity(new UrlEncodedFormEntity(pairs));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            try {
                HttpResponse response = client.execute(post);
                response.getStatusLine().getStatusCode();
            } catch (Exception e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}