com.andybotting.tramhunter.activity.StopDetailsActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.andybotting.tramhunter.activity.StopDetailsActivity.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.tramhunter.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 android.app.AlertDialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.AdapterView.OnItemSelectedListener;

import com.andybotting.tramhunter.R;
import com.andybotting.tramhunter.dao.TramHunterDB;
import com.andybotting.tramhunter.objects.Favourite;
import com.andybotting.tramhunter.objects.FavouriteList;
import com.andybotting.tramhunter.objects.NextTram;
import com.andybotting.tramhunter.objects.Route;
import com.andybotting.tramhunter.objects.Stop;
import com.andybotting.tramhunter.objects.StopsList;
import com.andybotting.tramhunter.service.TramTrackerService;
import com.andybotting.tramhunter.service.TramTrackerServiceException;
import com.andybotting.tramhunter.service.TramTrackerServiceJSON;
import com.andybotting.tramhunter.service.TramTrackerServiceSOAP;
import com.andybotting.tramhunter.ui.UIUtils;
import com.andybotting.tramhunter.util.GenericUtil;
import com.andybotting.tramhunter.util.PreferenceHelper;

public class StopDetailsActivity extends ListActivity {

    private static final String TAG = "StopDetailsActivity";
    private static final boolean LOGV = Log.isLoggable(TAG, Log.INFO);

    private final static int MENU_ITEM_REFRESH = 0;
    private final static int MENU_ITEM_FAVOURITE = 1;
    private final static int MENU_ITEM_MAP = 2;

    private TramHunterDB mDB;
    private CompoundButton mStarButton;

    private List<NextTram> mNextTrams = new ArrayList<NextTram>();
    private Stop mStop;
    private Route mRoute = null;
    private int mTramTrackerId;
    private volatile Thread mRefreshThread;

    boolean mLoadingError = false;
    boolean mShowDialog = true;

    private ListAdapter mListAdapter;
    private ListView mListView;
    private Spinner mRoutesSpinner;
    private ArrayAdapter<CharSequence> mAdapterForSpinner;

    private PreferenceHelper mPreferenceHelper;
    private FavouriteList mFavouriteList;
    private TramTrackerService ttService;

    private String mErrorMessage = null;
    private int mErrorRetry = 0;
    private final int MAX_ERRORS = 3;
    private boolean mFirstDepartureReqest = true;

    private static final int REFRESH_SECONDS = 60;

    // Handle the timer
    Handler UpdateHandler = new Handler() {
        public void handleMessage(Message msg) {
            new GetNextTramTimes().execute();
        }
    };

    /**
     * On create of this class
     */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        setContentView(R.layout.stop_details);

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

        // Refresh title button
        findViewById(R.id.title_btn_refresh).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mShowDialog = true;
                new GetNextTramTimes().execute();
            }
        });

        // Map title button
        findViewById(R.id.title_btn_map).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                StopsList mStopList = new StopsList();
                mStopList.add(mStop);
                bundle.putParcelable("stopslist", mStopList);
                Intent intent = new Intent(StopDetailsActivity.this, StopMapActivity.class);
                intent.putExtras(bundle);
                startActivityForResult(intent, 1);
            }
        });

        // Set up our list
        mListAdapter = new NextTramsListAdapter();
        mListView = getListView();
        mListView.setVisibility(View.GONE);

        // Preferences
        mPreferenceHelper = new PreferenceHelper();

        // Get bundle data
        int routeId = -1;
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            mTramTrackerId = extras.getInt("tramTrackerId");
            routeId = extras.getInt("routeId", -1);
        }

        // Get our favourite stops list
        mFavouriteList = new FavouriteList();

        // Create out DB instance
        mDB = new TramHunterDB();
        mStop = mDB.getStop(mTramTrackerId);
        if (routeId > -1)
            mRoute = mDB.getRoute(routeId);

        // Set the title
        String title = mStop.getStopName();
        ((TextView) findViewById(R.id.title_text)).setText(title);

        // Display stop data
        displayStop(mStop);

        // Star button
        mStarButton = (CompoundButton) findViewById(R.id.stopStar);
        mStarButton.setChecked(mFavouriteList.isFavourite(new Favourite(mStop, mRoute)));

        mStarButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mFavouriteList.setFavourite(new Favourite(mStop, mRoute), mStarButton.isChecked());
            }
        });

        // Get our TramTracker service, either SOAP (def) or JSON
        //      if (mPreferenceHelper.isJSONAPIEnabled())
        //         ttService = new TramTrackerServiceJSON();
        //      else
        ttService = new TramTrackerServiceSOAP();

        // Our thread for updating the stops every 60 secs
        mRefreshThread = new Thread(new CountDown());
        mRefreshThread.setDaemon(true);
        mRefreshThread.start();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mDB.close();
    }

    @Override
    public void onStop() {
        super.onStop();
        mRefreshThread.interrupt();
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mRefreshThread.isInterrupted()) {
            mRefreshThread.start();
        }
    }

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

        // Only 1 entry means an error, so make it < 2
        if (mListAdapter.getCount() < 2)
            mListView.getEmptyView().setVisibility(isRefreshing ? View.GONE : View.VISIBLE);
        else
            mListView.setVisibility(isRefreshing ? View.GONE : View.VISIBLE);

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

    /**
     * Update refresh status icon/views
     */
    private void showRefreshSpinner(boolean isRefreshing) {
        findViewById(R.id.title_btn_refresh).setVisibility(isRefreshing ? View.GONE : View.VISIBLE);
        findViewById(R.id.title_refresh_progress).setVisibility(isRefreshing ? View.VISIBLE : View.GONE);
    }

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

        menu.add(0, MENU_ITEM_REFRESH, 0, "Refresh");
        MenuItem menuItem1 = menu.findItem(MENU_ITEM_REFRESH);
        menuItem1.setIcon(R.drawable.ic_menu_refresh);

        menu.add(0, MENU_ITEM_FAVOURITE, 0, ""); // Title set in onMenuOpened()
        MenuItem menuItem2 = menu.findItem(MENU_ITEM_FAVOURITE);
        menuItem2.setIcon(R.drawable.ic_menu_star);

        menu.add(0, MENU_ITEM_MAP, 0, "Map");
        MenuItem menuItem3 = menu.findItem(MENU_ITEM_MAP);
        menuItem3.setIcon(R.drawable.ic_menu_mapmode);

        return true;
    }

    /**
     * Overide the onMenuOpened method to allow us to dynamically set the menu item
     * title on open.
     */
    @Override
    public boolean onMenuOpened(int featureId, Menu menu) {
        boolean isFavourite = mFavouriteList.isFavourite(new Favourite(mStop, mRoute));
        menu.getItem(MENU_ITEM_FAVOURITE).setTitle(isFavourite ? "Unfavourite" : "Favourite");
        return super.onMenuOpened(featureId, menu);
    }

    /**
     * Menu actions
     * @param menuItem
     */
    @Override
    public boolean onOptionsItemSelected(final MenuItem menuItem) {

        switch (menuItem.getItemId()) {

        case MENU_ITEM_REFRESH:
            mShowDialog = true;
            new GetNextTramTimes().execute();
            return true;

        case MENU_ITEM_FAVOURITE:
            mStarButton = (CompoundButton) findViewById(R.id.stopStar);
            Favourite favourite = new Favourite(mStop, mRoute);
            boolean isFavourite = mFavouriteList.isFavourite(favourite);
            mFavouriteList.toggleFavourite(favourite);
            mStarButton.setChecked(!isFavourite);
            return true;

        case MENU_ITEM_MAP:
            // Map view
            Bundle bundle = new Bundle();
            StopsList mStopList = new StopsList();
            mStopList.add(mStop);
            bundle.putParcelable("stopslist", mStopList);
            Intent intent = new Intent(StopDetailsActivity.this, StopMapActivity.class);
            intent.putExtras(bundle);
            startActivityForResult(intent, 1);
            return true;
        }

        return false;
    }

    /**
     * Display the details for a given stop
     * @param stop
     */
    private void displayStop(Stop stop) {

        // Set labels from Stop hash map
        String firstLineText = stop.getPrimaryName();

        String secondLineText = "Stop " + stop.getFlagStopNumber();
        // If the stop has a secondary name, add it
        if (stop.getSecondaryName().length() > 0) {
            secondLineText += ": " + stop.getSecondaryName();
        }
        secondLineText += " - " + stop.getCityDirection();
        secondLineText += " (" + stop.getTramTrackerID() + ")";

        //initialiseDatabase();
        final List<Route> mRoutes = mDB.getRoutesForStop(mTramTrackerId);
        stop.setRoutes(mRoutes);

        String thirdLineText = stop.getRoutesString();

        ((TextView) findViewById(R.id.stopNameTextView)).setText(firstLineText);
        ((TextView) findViewById(R.id.stopDetailsTextView)).setText(secondLineText);
        ((TextView) findViewById(R.id.stopRoutesTextView)).setText(thirdLineText);

        // If we have more than one route for this stop, then show the spinner
        if (mRoutes.size() > 1) {
            mRoutesSpinner = (Spinner) findViewById(R.id.routeSelectSpinner);
            mRoutesSpinner.setVisibility(View.VISIBLE);
            mAdapterForSpinner = new ArrayAdapter<CharSequence>(this, android.R.layout.simple_spinner_item);
            mAdapterForSpinner.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            mRoutesSpinner.setAdapter(mAdapterForSpinner);

            // Add 'All'
            mAdapterForSpinner.add("All Routes");

            Route r;
            for (int i = 0; i < mRoutes.size(); i++) {
                r = mRoutes.get(i);
                mAdapterForSpinner.add("Route " + r.getNumber());

                // If we have a route already (e.g. passed from Favourites activity, then
                // select it right now
                if (mRoute != null) {
                    if (mRoute.getId() == r.getId()) {
                        // i+1 because we have 'All Route' in position 0
                        mRoutesSpinner.setSelection(i + 1);
                    }
                }

            }

            mRoutesSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    if (id == 0) {
                        mRoute = null;
                    } else {
                        // -1 for the offset of having 'All Routes' first item
                        mRoute = mRoutes.get(position - 1);
                        if (LOGV)
                            Log.v(TAG, "Route selected: " + mRoute);

                    }

                    // Refresh the results
                    mStarButton.setChecked(mFavouriteList.isFavourite(new Favourite(mStop, mRoute)));
                    mShowDialog = true;
                    new GetNextTramTimes().execute();
                }

                public void onNothingSelected(AdapterView<?> parent) {
                    mRoute = null;
                }
            });
        }

    }

    /**
     * Create a countdown thread
     * @author andy
     *
     */
    private class CountDown implements Runnable {
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                Message m = new Message();
                UpdateHandler.sendMessage(m);
                try {
                    // 60 Seconds
                    Thread.sleep(REFRESH_SECONDS * 1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Background task for fetching tram times
     */
    private class GetNextTramTimes extends AsyncTask<NextTram, Void, List<NextTram>> {

        @Override
        protected void onPreExecute() {
            // Show the spinner in the title bar
            if (mShowDialog)
                showLoadingView(true);

            showRefreshSpinner(true);
        }

        @Override
        protected List<NextTram> doInBackground(final NextTram... params) {
            try {
                mNextTrams = ttService.getNextPredictedRoutesCollection(mStop, mRoute);

                for (int i = 0; i < mNextTrams.size(); i++) {
                    // Remove any entries that show 'anyType{}' - they are errors
                    if (mNextTrams.get(i).getDestination().matches("anyType"))
                        mNextTrams.remove(i);
                }
            } catch (TramTrackerServiceException e) {
                // Retry a couple of times before error
                if (mErrorRetry < MAX_ERRORS) {
                    mErrorRetry++;
                    if (LOGV)
                        Log.e(TAG, "Error " + mErrorRetry + " of " + MAX_ERRORS + ": " + e);
                    this.doInBackground(params);
                } else {
                    // Save the error message for the toast
                    mErrorMessage = e.getMessage();
                }
            }
            return mNextTrams;
        }

        @Override
        protected void onPostExecute(List<NextTram> nextTrams) {
            if (mErrorRetry == MAX_ERRORS) {

                // Toast: Error fetching departure information
                if (mFirstDepartureReqest)
                    UIUtils.popToast(getApplicationContext(),
                            getResources().getText(R.string.dialog_error_fetching) + ": \n(" + mErrorMessage + ")");

                mErrorMessage = null;
                mErrorRetry = 0;
            } else {
                boolean noResults = true;

                if (nextTrams.size() > 0) {
                    mLoadingError = false;

                    // Sort trams by minutesAway
                    Collections.sort(nextTrams);

                    // Show trams list, only if more than one tram.
                    if (nextTrams.size() > 1) {
                        noResults = false;
                        setListAdapter(mListAdapter);
                    }

                    // If it's the first load of data
                    if (mFirstDepartureReqest) {
                        // > 10 because ksoap2 fills in anytype{} instead of null
                        String specialEventMessage = nextTrams.get(0).getSpecialEventMessage();
                        if (specialEventMessage.length() > 10)
                            showSpecialEvent(specialEventMessage);

                        if (mPreferenceHelper.isSendStatsEnabled()) {
                            new Thread() {
                                public void run() {
                                    uploadStats();
                                }
                            }.start();
                        }

                        // Reset the first departure request
                        mFirstDepartureReqest = false;
                    }
                }

                if (noResults) {
                    mListView.getEmptyView().setVisibility(noResults ? View.VISIBLE : View.GONE);
                    mListView.setVisibility(noResults ? View.VISIBLE : View.GONE);
                }

            }

            // Hide the loading spinners
            showLoadingView(false);
            showRefreshSpinner(false);
            mShowDialog = false;
        }
    }

    /**
     * Show a dialog message for a given 'Special Event'
     * @param message
     */
    private void showSpecialEvent(String message) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
        dialogBuilder.setTitle("Special Event");
        dialogBuilder.setMessage(message);
        dialogBuilder.setPositiveButton("OK", null);
        dialogBuilder.setIcon(R.drawable.ic_dialog_alert);
        dialogBuilder.show();
    }

    /**
     * Create a NextTramsListAdapter for showing our next trams
     * @author andy
     *
     */
    private class NextTramsListAdapter extends BaseAdapter {

        public int getCount() {
            return mNextTrams.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.stop_details_row, parent, false);
            } else {
                pv = convertView;
            }

            NextTram thisTram = (NextTram) mNextTrams.get(position);
            ((TextView) pv.findViewById(R.id.routeNumber)).setText(thisTram.getRouteNo());
            ((TextView) pv.findViewById(R.id.routeDestination)).setText(thisTram.getDestination());
            ((TextView) pv.findViewById(R.id.nextTime)).setText(thisTram.humanMinutesAway());

            if (mPreferenceHelper.isTramImageEnabled()) {
                mDB = new TramHunterDB();

                int tramNumber = thisTram.getVehicleNo();
                if (LOGV)
                    Log.v(TAG, thisTram.toString() + " has tram number: " + tramNumber);

                if (tramNumber > 0) {
                    String tramClass = mDB.getTramClass(tramNumber);
                    String tramClassImage = UIUtils.getTramImage(tramClass);

                    if (tramClassImage != null) {
                        if (LOGV)
                            Log.v(TAG, "Tram Class: " + tramClass + " Tram Image: " + tramClassImage);
                        int resID = getResources().getIdentifier(tramClassImage, "drawable", getPackageName());
                        ((ImageView) pv.findViewById(R.id.tramClass)).setPadding(3, 5, 3, 3);
                        ((ImageView) pv.findViewById(R.id.tramClass)).setBackgroundResource(resID);
                    }
                }
                mDB.close();
            }
            return pv;
        }

    }

    /**
     * Upload statistics to our web server
     */
    private void uploadStats() {
        if (LOGV)
            Log.i(TAG, "Sending stop request 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://tramhunter.andybotting.com/stats/stop/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("guid", ttService.getGUID()));
            pairs.add(new BasicNameValuePair("ttid", String.valueOf(mStop.getTramTrackerID())));

            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();
        }

    }

}