329 lines
9.5 KiB
Java
329 lines
9.5 KiB
Java
package com.dougkeen.bart;
|
|
|
|
import java.util.List;
|
|
|
|
import android.app.ListActivity;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.database.Cursor;
|
|
import android.net.Uri;
|
|
import android.opengl.Visibility;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.os.Parcelable;
|
|
import android.os.PowerManager;
|
|
import android.text.format.DateFormat;
|
|
import android.text.util.Linkify;
|
|
import android.util.Log;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.dougkeen.bart.GetRealTimeDeparturesTask.Params;
|
|
import com.dougkeen.bart.data.Departure;
|
|
import com.dougkeen.bart.data.RealTimeDepartures;
|
|
import com.dougkeen.bart.data.RoutesColumns;
|
|
|
|
public class ViewDeparturesActivity extends ListActivity {
|
|
|
|
private static final int UNCERTAINTY_THRESHOLD = 17;
|
|
|
|
private Uri mUri;
|
|
|
|
private Station mOrigin;
|
|
private Station mDestination;
|
|
|
|
private ArrayAdapter<Departure> mDeparturesAdapter;
|
|
|
|
private TextView mListTitleView;
|
|
|
|
private AsyncTask<Params, Integer, RealTimeDepartures> mGetDeparturesTask;
|
|
|
|
private boolean mIsAutoUpdating = false;
|
|
|
|
private final Runnable AUTO_UPDATE_RUNNABLE = new Runnable() {
|
|
public void run() {
|
|
runAutoUpdate();
|
|
}
|
|
};
|
|
|
|
private PowerManager.WakeLock mWakeLock;
|
|
|
|
private boolean mDataFetchIsPending;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.main);
|
|
|
|
final Intent intent = getIntent();
|
|
|
|
String action = intent.getAction();
|
|
|
|
if (Intent.ACTION_VIEW.equals(action)) {
|
|
mUri = intent.getData();
|
|
}
|
|
|
|
Cursor cursor = managedQuery(mUri, new String[] {
|
|
RoutesColumns.FROM_STATION.string,
|
|
RoutesColumns.TO_STATION.string }, null, null, null);
|
|
|
|
if (!cursor.moveToFirst()) {
|
|
throw new IllegalStateException("URI not found: " + mUri.toString());
|
|
}
|
|
mOrigin = Station.getByAbbreviation(cursor.getString(0));
|
|
mDestination = Station.getByAbbreviation(cursor.getString(1));
|
|
|
|
String header = "Departures:\n" + mOrigin.name + " to "
|
|
+ mDestination.name;
|
|
|
|
mListTitleView = (TextView) findViewById(R.id.listTitle);
|
|
mListTitleView.setText(header);
|
|
((TextView) findViewById(android.R.id.empty))
|
|
.setText(R.string.departure_wait_message);
|
|
|
|
mDeparturesAdapter = new DepartureArrayAdapter(this,
|
|
R.layout.departure_listing);
|
|
if (savedInstanceState != null
|
|
&& savedInstanceState.containsKey("departures")) {
|
|
for (Parcelable departure : savedInstanceState
|
|
.getParcelableArray("departures")) {
|
|
mDeparturesAdapter.add((Departure) departure);
|
|
}
|
|
}
|
|
setListAdapter(mDeparturesAdapter);
|
|
|
|
findViewById(R.id.missingDepartureText).setVisibility(View.VISIBLE);
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
cancelDataFetch();
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
cancelDataFetch();
|
|
super.onDestroy();
|
|
}
|
|
|
|
private void cancelDataFetch() {
|
|
if (mGetDeparturesTask != null) {
|
|
mGetDeparturesTask.cancel(true);
|
|
mDataFetchIsPending = false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
Departure[] departures = new Departure[mDeparturesAdapter.getCount()];
|
|
for (int i = mDeparturesAdapter.getCount() - 1; i >= 0; i--) {
|
|
departures[i] = mDeparturesAdapter.getItem(i);
|
|
}
|
|
outState.putParcelableArray("departures", departures);
|
|
}
|
|
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
super.onWindowFocusChanged(hasFocus);
|
|
if (hasFocus) {
|
|
if (!mDataFetchIsPending) {
|
|
fetchLatestDepartures();
|
|
}
|
|
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
mWakeLock = powerManager
|
|
.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
|
|
"ViewDeparturesActivity");
|
|
mWakeLock.acquire();
|
|
if (mDeparturesAdapter != null && !mDeparturesAdapter.isEmpty()) {
|
|
mIsAutoUpdating = true;
|
|
}
|
|
runAutoUpdate();
|
|
} else if (mWakeLock != null) {
|
|
mWakeLock.release();
|
|
}
|
|
}
|
|
|
|
private void fetchLatestDepartures() {
|
|
if (!hasWindowFocus())
|
|
return;
|
|
if (mGetDeparturesTask != null
|
|
&& mGetDeparturesTask.getStatus().equals(
|
|
AsyncTask.Status.RUNNING)) {
|
|
// Don't overlap fetches
|
|
return;
|
|
}
|
|
|
|
mGetDeparturesTask = new GetRealTimeDeparturesTask() {
|
|
@Override
|
|
public void onResult(RealTimeDepartures result) {
|
|
mDataFetchIsPending = false;
|
|
Log.i(Constants.TAG, "Processing data from server");
|
|
processLatestDepartures(result);
|
|
Log.i(Constants.TAG, "Done processing data from server");
|
|
}
|
|
|
|
@Override
|
|
public void onError(Exception e) {
|
|
mDataFetchIsPending = false;
|
|
Log.w(Constants.TAG, e.getMessage(), e);
|
|
Toast.makeText(ViewDeparturesActivity.this,
|
|
R.string.could_not_connect, Toast.LENGTH_LONG).show();
|
|
((TextView) findViewById(android.R.id.empty))
|
|
.setText(R.string.could_not_connect);
|
|
// Try again in 60s
|
|
scheduleDataFetch(60000);
|
|
}
|
|
};
|
|
Log.i(Constants.TAG, "Fetching data from server");
|
|
mGetDeparturesTask.execute(new GetRealTimeDeparturesTask.Params(
|
|
mOrigin, mDestination));
|
|
}
|
|
|
|
protected void processLatestDepartures(RealTimeDepartures result) {
|
|
if (result.getDepartures().isEmpty()) {
|
|
final TextView textView = (TextView) findViewById(android.R.id.empty);
|
|
textView.setText(R.string.no_data_message);
|
|
Linkify.addLinks(textView, Linkify.WEB_URLS);
|
|
return;
|
|
}
|
|
|
|
boolean needsBetterAccuracy = false;
|
|
Departure firstDeparture = null;
|
|
final List<Departure> departures = result.getDepartures();
|
|
if (mDeparturesAdapter.getCount() > 0) {
|
|
int adapterIndex = -1;
|
|
for (Departure departure : departures) {
|
|
adapterIndex++;
|
|
Departure existingDeparture = null;
|
|
if (adapterIndex < mDeparturesAdapter.getCount()) {
|
|
existingDeparture = mDeparturesAdapter
|
|
.getItem(adapterIndex);
|
|
}
|
|
while (existingDeparture != null
|
|
&& !departure.equals(existingDeparture)) {
|
|
mDeparturesAdapter.remove(existingDeparture);
|
|
if (adapterIndex < mDeparturesAdapter.getCount()) {
|
|
existingDeparture = mDeparturesAdapter
|
|
.getItem(adapterIndex);
|
|
} else {
|
|
existingDeparture = null;
|
|
}
|
|
}
|
|
if (existingDeparture != null) {
|
|
existingDeparture.mergeEstimate(departure);
|
|
} else {
|
|
mDeparturesAdapter.add(departure);
|
|
existingDeparture = departure;
|
|
}
|
|
if (firstDeparture == null) {
|
|
firstDeparture = existingDeparture;
|
|
}
|
|
if (existingDeparture.getUncertaintySeconds() > UNCERTAINTY_THRESHOLD) {
|
|
needsBetterAccuracy = true;
|
|
}
|
|
}
|
|
} else {
|
|
for (Departure departure : departures) {
|
|
if (firstDeparture == null) {
|
|
firstDeparture = departure;
|
|
}
|
|
mDeparturesAdapter.add(departure);
|
|
}
|
|
needsBetterAccuracy = true;
|
|
}
|
|
mDeparturesAdapter.notifyDataSetChanged();
|
|
|
|
if (hasWindowFocus() && firstDeparture != null) {
|
|
if (needsBetterAccuracy || firstDeparture.hasDeparted()) {
|
|
// Get more data in 20s
|
|
scheduleDataFetch(20000);
|
|
} else {
|
|
// Get more 90 seconds before next train arrives, right when
|
|
// next train arrives, or 3 minutes, whichever is sooner
|
|
final int intervalUntilNextDeparture = firstDeparture
|
|
.getMinSecondsLeft() * 1000;
|
|
final int alternativeInterval = 3 * 60 * 1000;
|
|
|
|
int interval = intervalUntilNextDeparture;
|
|
if (intervalUntilNextDeparture > 95000
|
|
&& intervalUntilNextDeparture < alternativeInterval) {
|
|
interval = interval - 90 * 1000;
|
|
} else if (intervalUntilNextDeparture > alternativeInterval) {
|
|
interval = alternativeInterval;
|
|
}
|
|
|
|
if (interval < 0) {
|
|
interval = 20000;
|
|
}
|
|
|
|
scheduleDataFetch(interval);
|
|
}
|
|
if (!mIsAutoUpdating) {
|
|
mIsAutoUpdating = true;
|
|
}
|
|
} else {
|
|
mIsAutoUpdating = false;
|
|
}
|
|
}
|
|
|
|
private void scheduleDataFetch(int millisUntilExecute) {
|
|
if (!mDataFetchIsPending) {
|
|
mListTitleView.postDelayed(new Runnable() {
|
|
public void run() {
|
|
fetchLatestDepartures();
|
|
}
|
|
}, millisUntilExecute);
|
|
mDataFetchIsPending = true;
|
|
Log.i(Constants.TAG, "Scheduled another data fetch in "
|
|
+ millisUntilExecute / 1000 + "s");
|
|
}
|
|
}
|
|
|
|
private void runAutoUpdate() {
|
|
if (mIsAutoUpdating && mDeparturesAdapter != null) {
|
|
mDeparturesAdapter.notifyDataSetChanged();
|
|
}
|
|
if (hasWindowFocus()) {
|
|
mListTitleView.postDelayed(AUTO_UPDATE_RUNNABLE, 1000);
|
|
} else {
|
|
mIsAutoUpdating = false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
MenuInflater inflater = getMenuInflater();
|
|
inflater.inflate(R.menu.route_menu, menu);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
int itemId = item.getItemId();
|
|
if (itemId == R.id.view_on_bart_site_button) {
|
|
startActivity(new Intent(
|
|
Intent.ACTION_VIEW,
|
|
Uri.parse("http://m.bart.gov/schedules/qp_results.aspx?type=departure&date=today&time="
|
|
+ DateFormat.format("h:mmaa",
|
|
System.currentTimeMillis())
|
|
+ "&orig="
|
|
+ mOrigin.abbreviation
|
|
+ "&dest="
|
|
+ mDestination.abbreviation)));
|
|
return true;
|
|
} else if (itemId == R.id.view_system_map_button) {
|
|
startActivity(new Intent(this, ViewMapActivity.class));
|
|
return true;
|
|
} else {
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
}
|
|
}
|