Reorganized code

Implemented countdowns on routes list screen
Implemented offline storage of last boarded departure selection
This commit is contained in:
Doug Keen 2012-09-25 14:31:06 -07:00
parent fae89f4c45
commit f12860d87e
26 changed files with 1647 additions and 1161 deletions

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@ -18,7 +18,8 @@
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="RoutesListActivity"
android:name=".activities.RoutesListActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -43,7 +44,7 @@
</intent-filter>
</activity>
<activity
android:name="ViewDeparturesActivity"
android:name=".activities.ViewDeparturesActivity"
android:label="@string/departures" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -55,7 +56,7 @@
</intent-filter>
</activity>
<activity
android:name="ViewMapActivity"
android:name=".activities.ViewMapActivity"
android:label="@string/system_map" >
</activity>
@ -66,14 +67,14 @@
android:label="BartRunner data provider" />
<service
android:name=".NotificationService"
android:name=".services.NotificationService"
android:exported="false" />
<service
android:name=".EtdService"
android:name=".services.EtdService"
android:exported="false" />
<receiver
android:name=".AlarmBroadcastReceiver"
android:name=".receivers.AlarmBroadcastReceiver"
android:exported="false" >
<intent-filter>
<action android:name="com.dougkeen.action.ALARM" />

View File

@ -1,23 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bart="http://schemas.android.com/apk/res/com.dougkeen.bart"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="5dp"
android:paddingRight="5dp" >
<TextView
android:id="@+id/fareText"
style="@style/FareTextView"
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/countdownText"
style="@style/DepartureCountdownText"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:maxLines="3" />
android:layout_alignParentTop="true"
android:gravity="right"
android:width="90dp"
bart:tickInterval="1" />
<com.dougkeen.bart.controls.TimedTextSwitcher
android:id="@+id/uncertainty"
style="@style/DepartureUncertaintyText"
android:layout_alignParentRight="true"
android:layout_below="@id/countdownText"
bart:tickInterval="1" />
<TextView
android:id="@+id/originText"
style="@style/FavoriteListingTextView"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/fareText"
android:layout_toLeftOf="@id/countdownText"
android:text="Origin" />
<TextView
@ -32,7 +42,7 @@
android:id="@+id/destinationText"
style="@style/FavoriteListingTextView"
android:layout_below="@id/originText"
android:layout_toLeftOf="@id/fareText"
android:layout_toLeftOf="@id/countdownText"
android:layout_toRightOf="@id/to"
android:text="Destination" />

View File

@ -17,6 +17,14 @@
android:layout_weight="1"
android:visibility="gone" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="1000" />
<FrameLayout
style="ButtonBar"
android:layout_width="match_parent"

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/DepartureUncertaintyText" />

View File

@ -4,6 +4,7 @@
<string name="app_name">BART Runner</string>
<string name="favorite_routes">Favorite routes</string>
<string name="empty_favorites_list_message">No favorite routes have been added yet</string>
<string name="loading_message">Loading, please wait...</string>
<string name="add_route">Add a route</string>
<string name="origin">Origin</string>
<string name="destination">Destination</string>

View File

@ -1,12 +1,27 @@
package com.dougkeen.bart;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import android.app.Application;
import android.media.MediaPlayer;
import android.os.Parcel;
import android.util.Log;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure;
import com.dougkeen.util.Observable;
public class BartRunnerApplication extends Application {
private static final int FIVE_MINUTES = 5 * 60 * 1000;
private static final String CACHE_FILE_NAME = "lastBoardedDeparture";
private Departure mBoardedDeparture;
private Observable<Boolean> mAlarmPending = new Observable<Boolean>(false);
@ -26,11 +41,73 @@ public class BartRunnerApplication extends Application {
}
public Departure getBoardedDeparture() {
if (mBoardedDeparture == null) {
// see if there's a saved one
File cachedDepartureFile = new File(getCacheDir(), CACHE_FILE_NAME);
if (cachedDepartureFile.exists()) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(cachedDepartureFile);
byte[] byteArray = IOUtils.toByteArray(inputStream);
Parcel parcel = Parcel.obtain();
parcel.unmarshall(byteArray, 0, byteArray.length);
parcel.setDataPosition(0);
Departure lastBoardedDeparture = Departure.CREATOR
.createFromParcel(parcel);
/*
* Check if the cached one is relatively recent. If so,
* restore that to the application context
*/
long now = System.currentTimeMillis();
if (lastBoardedDeparture.getEstimatedArrivalTime() >= now
- FIVE_MINUTES
|| lastBoardedDeparture.getMeanEstimate() >= now
- 2 * FIVE_MINUTES) {
mBoardedDeparture = lastBoardedDeparture;
}
} catch (Exception e) {
Log.w(Constants.TAG,
"Couldn't read or unmarshal lastBoardedDeparture file",
e);
try {
cachedDepartureFile.delete();
} catch (SecurityException anotherException) {
Log.w(Constants.TAG,
"Couldn't delete lastBoardedDeparture file",
anotherException);
}
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}
return mBoardedDeparture;
}
public void setBoardedDeparture(Departure boardedDeparture) {
this.mBoardedDeparture = boardedDeparture;
if (!ObjectUtils.equals(boardedDeparture, mBoardedDeparture)
|| ObjectUtils.compare(mBoardedDeparture, boardedDeparture) != 0) {
this.mBoardedDeparture = boardedDeparture;
if (mBoardedDeparture != null) {
File cachedDepartureFile = new File(getCacheDir(),
CACHE_FILE_NAME);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cachedDepartureFile);
Parcel parcel = Parcel.obtain();
mBoardedDeparture.writeToParcel(parcel, 0);
fileOutputStream.write(parcel.marshall());
} catch (Exception e) {
Log.w(Constants.TAG,
"Couldn't write last boarded departure cache file",
e);
} finally {
IOUtils.closeQuietly(fileOutputStream);
}
}
}
}
public boolean isAlarmSounding() {

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import android.app.Dialog;
import android.content.Context;
@ -13,6 +13,7 @@ import android.widget.Spinner;
import android.widget.Toast;
import com.WazaBe.HoloEverywhere.AlertDialog;
import com.dougkeen.bart.R;
import com.dougkeen.bart.model.Station;
public abstract class AbstractRouteSelectionFragment extends DialogFragment {

View File

@ -1,9 +1,10 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import android.content.ContentValues;
import android.view.View;
import android.widget.CheckBox;
import com.dougkeen.bart.R;
import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station;

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import android.content.Intent;

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import java.util.Calendar;
import java.util.TimeZone;
@ -8,17 +8,16 @@ import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.SimpleCursorAdapter.ViewBinder;
import android.widget.TextView;
import com.WazaBe.HoloEverywhere.AlertDialog;
@ -27,18 +26,23 @@ import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.Ticker;
import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.FavoritesArrayAdapter;
import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.networktasks.GetRouteFareTask;
public class RoutesListActivity extends SherlockFragmentActivity {
public class RoutesListActivity extends SherlockFragmentActivity implements
LoaderCallbacks<Cursor> {
private static final int FAVORITES_LOADER_ID = 0;
private static final TimeZone PACIFIC_TIME = TimeZone
.getTimeZone("America/Los_Angeles");
protected Cursor mQuery;
private Uri mCurrentlySelectedUri;
private Station mCurrentlySelectedOrigin;
@ -46,6 +50,8 @@ public class RoutesListActivity extends SherlockFragmentActivity {
private ActionMode mActionMode;
private FavoritesArrayAdapter mRoutesAdapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
@ -53,51 +59,24 @@ public class RoutesListActivity extends SherlockFragmentActivity {
setContentView(R.layout.main);
setTitle(R.string.favorite_routes);
mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] {
RoutesColumns._ID.string, RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string, RoutesColumns.FARE.string,
RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null,
RoutesColumns._ID.string);
mRoutesAdapter = new FavoritesArrayAdapter(this,
R.layout.favorite_listing);
refreshFares();
getSupportLoaderManager().initLoader(FAVORITES_LOADER_ID, null, this);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.favorite_listing, mQuery, new String[] {
RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string,
RoutesColumns.FARE.string }, new int[] {
R.id.originText, R.id.destinationText, R.id.fareText });
adapter.setViewBinder(new ViewBinder() {
public boolean setViewValue(View view, Cursor cursor,
int columnIndex) {
if (view.getId() == R.id.fareText) {
String fare = cursor.getString(columnIndex);
if (fare != null) {
((TextView) view).setSingleLine(false);
((TextView) view).setText("Fare:\n" + fare);
}
} else {
((TextView) view).setText(Station.getByAbbreviation(cursor
.getString(columnIndex)).name);
}
return true;
}
});
setListAdapter(adapter);
setListAdapter(mRoutesAdapter);
getListView().setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> l, View v,
int position, long id) {
;
startActivity(new Intent(Intent.ACTION_VIEW,
ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, id)));
Constants.FAVORITE_CONTENT_URI,
getListAdapter().getItem(position)
.getId())));
}
});
getListView().setEmptyView(findViewById(android.R.id.empty));
getListView().setOnItemLongClickListener(
@ -109,17 +88,13 @@ public class RoutesListActivity extends SherlockFragmentActivity {
mActionMode.finish();
}
mCurrentlySelectedUri = ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, id);
StationPair item = getListAdapter().getItem(position);
CursorWrapper item = (CursorWrapper) getListAdapter()
.getItem(position);
Station orig = Station.getByAbbreviation(CursorUtils
.getString(item, RoutesColumns.FROM_STATION));
Station dest = Station.getByAbbreviation(CursorUtils
.getString(item, RoutesColumns.TO_STATION));
mCurrentlySelectedOrigin = orig;
mCurrentlySelectedDestination = dest;
mCurrentlySelectedUri = ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, item.getId());
mCurrentlySelectedOrigin = item.getOrigin();
mCurrentlySelectedDestination = item.getDestination();
startContextualActionMode();
return true;
@ -157,31 +132,58 @@ public class RoutesListActivity extends SherlockFragmentActivity {
}
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this, Constants.FAVORITE_CONTENT_URI,
new String[] { RoutesColumns._ID.string,
RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string,
RoutesColumns.FARE.string,
RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null,
RoutesColumns._ID.string);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor.getCount() == 0) {
((TextView) findViewById(android.R.id.empty))
.setText(R.string.empty_favorites_list_message);
}
mRoutesAdapter.updateFromCursor(cursor);
refreshFares(cursor);
findViewById(R.id.progress).setVisibility(View.GONE);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Nothing to do
}
@SuppressWarnings("unchecked")
private AdapterView<ListAdapter> getListView() {
return (AdapterView<ListAdapter>) findViewById(android.R.id.list);
}
private CursorAdapter mListAdapter;
protected CursorAdapter getListAdapter() {
return mListAdapter;
protected FavoritesArrayAdapter getListAdapter() {
return mRoutesAdapter;
}
protected void setListAdapter(SimpleCursorAdapter adapter) {
mListAdapter = adapter;
getListView().setAdapter(mListAdapter);
protected void setListAdapter(FavoritesArrayAdapter adapter) {
mRoutesAdapter = adapter;
getListView().setAdapter(mRoutesAdapter);
}
private void refreshFares() {
if (mQuery.moveToFirst()) {
private void refreshFares(Cursor cursor) {
if (cursor.moveToFirst()) {
do {
final Station orig = Station.getByAbbreviation(CursorUtils
.getString(mQuery, RoutesColumns.FROM_STATION));
.getString(cursor, RoutesColumns.FROM_STATION));
final Station dest = Station.getByAbbreviation(CursorUtils
.getString(mQuery, RoutesColumns.TO_STATION));
final Long id = CursorUtils.getLong(mQuery, RoutesColumns._ID);
final Long lastUpdateMillis = CursorUtils.getLong(mQuery,
.getString(cursor, RoutesColumns.TO_STATION));
final Long id = CursorUtils.getLong(cursor, RoutesColumns._ID);
final Long lastUpdateMillis = CursorUtils.getLong(cursor,
RoutesColumns.FARE_LAST_UPDATED);
Calendar now = Calendar.getInstance();
@ -215,7 +217,7 @@ public class RoutesListActivity extends SherlockFragmentActivity {
};
fareTask.execute(new GetRouteFareTask.Params(orig, dest));
}
} while (mQuery.moveToNext());
} while (cursor.moveToNext());
}
}
@ -235,9 +237,41 @@ public class RoutesListActivity extends SherlockFragmentActivity {
@Override
protected void onResume() {
super.onResume();
((TextView) findViewById(android.R.id.empty))
.setText(R.string.empty_favorites_list_message);
refreshFares();
Ticker.getInstance().startTicking(this);
if (mRoutesAdapter != null && !mRoutesAdapter.isEmpty()
&& !mRoutesAdapter.areEtdListenersActive()) {
mRoutesAdapter.setUpEtdListeners();
}
}
@Override
protected void onPause() {
super.onPause();
if (mRoutesAdapter != null && mRoutesAdapter.areEtdListenersActive()) {
mRoutesAdapter.clearEtdListeners();
}
}
@Override
protected void onStop() {
super.onStop();
Ticker.getInstance().stopTicking(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mRoutesAdapter != null) {
mRoutesAdapter.close();
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Ticker.getInstance().startTicking(this);
}
}
public boolean onCreateOptionsMenu(Menu menu) {

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import net.simonvt.widget.NumberPicker;
import android.app.Dialog;
@ -12,6 +12,9 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import com.WazaBe.HoloEverywhere.AlertDialog;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.services.NotificationService;
public class TrainAlertDialogFragment extends DialogFragment {

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import java.util.List;
@ -39,17 +39,23 @@ import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.dougkeen.bart.EtdService.EtdServiceBinder;
import com.dougkeen.bart.EtdService.EtdServiceListener;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.controls.Ticker;
import com.dougkeen.bart.data.DepartureArrayAdapter;
import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.model.TextProvider;
import com.dougkeen.bart.services.EtdService;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener;
import com.dougkeen.bart.services.NotificationService;
import com.dougkeen.util.Observer;
import com.dougkeen.util.WakeLocker;
public class ViewDeparturesActivity extends SherlockFragmentActivity implements
EtdServiceListener {
@ -124,11 +130,10 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements
.getString(0));
mDestination = Station.getByAbbreviation(cursor
.getString(1));
cursor.close();
setListTitle();
if (mBound && mEtdService != null)
mEtdService
.registerListener(ViewDeparturesActivity.this);
mEtdService.registerListener(
ViewDeparturesActivity.this, false);
refreshBoardedDeparture();
getSupportLoaderManager().destroyLoader(LOADER_ID);
@ -287,7 +292,8 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements
mEtdService = ((EtdServiceBinder) service).getService();
mBound = true;
if (getStationPair() != null) {
mEtdService.registerListener(ViewDeparturesActivity.this);
mEtdService
.registerListener(ViewDeparturesActivity.this, false);
}
}
};
@ -314,7 +320,7 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements
mAlarmPendingObserver);
if (mBound)
unbindService(mConnection);
Ticker.getInstance().stopTicking();
Ticker.getInstance().stopTicking(this);
WakeLocker.release();
}
@ -344,7 +350,7 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements
super.onStart();
bindService(new Intent(this, EtdService.class), mConnection,
Context.BIND_AUTO_CREATE);
Ticker.getInstance().startTicking();
Ticker.getInstance().startTicking(this);
}
@Override
@ -353,7 +359,7 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements
if (hasFocus) {
getWindow()
.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Ticker.getInstance().startTicking();
Ticker.getInstance().startTicking(this);
refreshBoardedDeparture();
}
}

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.activities;
import android.os.Bundle;
import android.webkit.WebView;
@ -7,6 +7,7 @@ import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.dougkeen.bart.R;
public class ViewMapActivity extends SherlockActivity {

View File

@ -37,7 +37,7 @@ public class CountdownTextView extends TextView implements
public void setTextProvider(TextProvider provider) {
mTextProvider = provider;
Ticker.getInstance().addSubscriber(this);
Ticker.getInstance().addSubscriber(this, getContext());
}
@Override

View File

@ -3,6 +3,7 @@ package com.dougkeen.bart.controls;
import java.util.Iterator;
import java.util.WeakHashMap;
import android.content.Context;
import android.os.Handler;
public class Ticker {
@ -16,6 +17,8 @@ public class Ticker {
private WeakHashMap<TickSubscriber, Object> mSubscribers;
private WeakHashMap<Context, Object> mTickerHosts;
private TickerEngine mEngine;
private static class TickerEngine implements Runnable {
@ -84,26 +87,28 @@ public class Ticker {
return sInstance;
}
public void addSubscriber(TickSubscriber subscriber) {
public void addSubscriber(TickSubscriber subscriber, Context host) {
if (!mSubscribers.containsKey(subscriber) && subscriber != null) {
mSubscribers.put(subscriber, null);
startTicking();
startTicking(host);
}
}
private Ticker() {
mSubscribers = new WeakHashMap<TickSubscriber, Object>();
mTickerHosts = new WeakHashMap<Context, Object>();
mEngine = new TickerEngine(this);
}
public void startTicking() {
public void startTicking(Context host) {
mTickerHosts.put(host, true);
if (!mEngine.isOn())
mEngine.run();
}
public void stopTicking() {
if (mEngine.isOn())
public void stopTicking(Context host) {
mTickerHosts.remove(host);
if (mEngine.isOn() && mTickerHosts.isEmpty())
mEngine.stop();
}
}

View File

@ -43,7 +43,7 @@ public class TimedTextSwitcher extends TextSwitcher implements
public void setTextProvider(TextProvider textProvider) {
mTextProvider = textProvider;
Ticker.getInstance().addSubscriber(this);
Ticker.getInstance().addSubscriber(this, getContext());
}
private CharSequence mLastText;

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.data;
import java.util.List;
@ -18,6 +18,7 @@ import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.controls.TimedTextSwitcher;
import com.dougkeen.bart.model.Departure;

View File

@ -0,0 +1,276 @@
package com.dougkeen.bart.data;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.RelativeLayout;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.controls.TimedTextSwitcher;
import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.model.TextProvider;
import com.dougkeen.bart.services.EtdService;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener;
public class FavoritesArrayAdapter extends ArrayAdapter<StationPair> {
private boolean mBound = false;
private EtdService mEtdService;
private Activity mHostActivity;
private Map<StationPair, EtdListener> mEtdListeners = new HashMap<StationPair, EtdListener>();
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
mEtdService = null;
mBound = false;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mEtdService = ((EtdServiceBinder) service).getService();
mBound = true;
if (!isEmpty()) {
setUpEtdListeners();
}
}
};
public void setUpEtdListeners() {
if (mBound && mEtdService != null) {
for (int i = getCount() - 1; i >= 0; i--) {
final StationPair item = getItem(i);
mEtdListeners.put(item, new EtdListener(item, mEtdService));
}
}
}
public void clearEtdListeners() {
if (mBound && mEtdService != null) {
for (EtdListener listener : mEtdListeners.values()) {
listener.close(mEtdService);
}
mEtdListeners.clear();
}
}
public boolean areEtdListenersActive() {
return !mEtdListeners.isEmpty();
}
public FavoritesArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
mHostActivity = (Activity) context;
mHostActivity.bindService(new Intent(mHostActivity, EtdService.class),
mConnection, Context.BIND_AUTO_CREATE);
}
public void close() {
if (mBound) {
mHostActivity.unbindService(mConnection);
}
}
@Override
public void add(StationPair object) {
super.add(object);
if (mEtdService != null && mBound) {
mEtdListeners.put(object, new EtdListener(object, mEtdService));
}
}
@Override
public void remove(StationPair object) {
super.remove(object);
if (mEtdListeners.containsKey(object) && mEtdService != null & mBound) {
mEtdListeners.get(object).close(mEtdService);
mEtdListeners.remove(object);
}
}
@Override
public void clear() {
super.clear();
clearEtdListeners();
}
public void updateFromCursor(Cursor cursor) {
if (!cursor.moveToFirst()) {
clear();
}
for (int i = 0; i < getCount(); i++) {
StationPair adapterItem = getItem(i);
StationPair cursorItem = StationPair.createFromCursor(cursor);
while (!cursorItem.equals(adapterItem)) {
remove(adapterItem);
if (i < getCount()) {
adapterItem = getItem(i);
} else {
break;
}
}
if (cursorItem.equals(adapterItem)
&& !cursorItem.fareEquals(adapterItem)) {
adapterItem.setFare(cursorItem.getFare());
adapterItem.setFareLastUpdated(cursorItem.getFareLastUpdated());
notifyDataSetChanged();
}
cursor.moveToNext();
}
while (!cursor.isAfterLast()) {
add(StationPair.createFromCursor(cursor));
cursor.moveToNext();
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null && convertView instanceof RelativeLayout) {
view = convertView;
} else {
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.favorite_listing, parent, false);
}
final StationPair pair = getItem(position);
final EtdListener etdListener = mEtdListeners.get(pair);
final TimedTextSwitcher uncertaintyTextSwitcher = (TimedTextSwitcher) view
.findViewById(R.id.uncertainty);
initTextSwitcher(uncertaintyTextSwitcher);
if (etdListener == null || etdListener.getFirstDeparture() == null) {
uncertaintyTextSwitcher.setCurrentText(pair.getFare());
} else {
CountdownTextView countdownTextView = (CountdownTextView) view
.findViewById(R.id.countdownText);
countdownTextView.setText(etdListener.getFirstDeparture()
.getCountdownText());
countdownTextView.setTextProvider(new TextProvider() {
@Override
public String getText(long tickNumber) {
return etdListener.getFirstDeparture().getCountdownText();
}
});
final String uncertaintyText = etdListener.getFirstDeparture()
.getUncertaintyText();
if (!StringUtils.isBlank(uncertaintyText)) {
uncertaintyTextSwitcher.setCurrentText(uncertaintyText);
} else {
uncertaintyTextSwitcher.setCurrentText(pair.getFare());
}
uncertaintyTextSwitcher.setTextProvider(new TextProvider() {
@Override
public String getText(long tickNumber) {
if (tickNumber % 4 <= 1) {
return pair.getFare();
} else {
return etdListener.getFirstDeparture()
.getUncertaintyText();
}
}
});
}
((TextView) view.findViewById(R.id.originText)).setText(pair
.getOrigin().name);
((TextView) view.findViewById(R.id.destinationText)).setText(pair
.getDestination().name);
return view;
}
private void initTextSwitcher(TextSwitcher textSwitcher) {
if (textSwitcher.getInAnimation() == null) {
textSwitcher.setFactory(new ViewFactory() {
public View makeView() {
return LayoutInflater.from(getContext()).inflate(
R.layout.uncertainty_textview, null);
}
});
textSwitcher.setInAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.slide_in_left));
textSwitcher.setOutAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.slide_out_right));
}
}
private class EtdListener implements EtdServiceListener {
private final StationPair mStationPair;
private Departure firstDeparture;
protected EtdListener(StationPair mStationPair, EtdService etdService) {
super();
this.mStationPair = mStationPair;
etdService.registerListener(this, true);
}
protected void close(EtdService etdService) {
etdService.unregisterListener(this);
}
@Override
public void onETDChanged(List<Departure> departures) {
for (Departure departure : departures) {
if (!departure.hasDeparted()) {
if (!departure.equals(firstDeparture)) {
firstDeparture = departure;
FavoritesArrayAdapter.this.notifyDataSetChanged();
}
return;
}
}
}
@Override
public void onError(String errorMessage) {
}
@Override
public void onRequestStarted() {
}
@Override
public void onRequestEnded() {
}
@Override
public StationPair getStationPair() {
return mStationPair;
}
public Departure getFirstDeparture() {
return firstDeparture;
}
}
}

View File

@ -496,8 +496,10 @@ public class Departure implements Parcelable, Comparable<Departure> {
dest.writeLong(arrivalTimeOverride);
dest.writeInt(estimatedTripTime);
dest.writeInt(line.ordinal());
dest.writeBooleanArray(new boolean[] { beganAsDeparted, bikeAllowed,
requiresTransfer, transferScheduled });
dest.writeByte(beganAsDeparted ? (byte) 1 : (byte) 0);
dest.writeByte(bikeAllowed ? (byte) 1 : (byte) 0);
dest.writeByte(requiresTransfer ? (byte) 1 : (byte) 0);
dest.writeByte(transferScheduled ? (byte) 1 : (byte) 0);
}
private void readFromParcel(Parcel in) {
@ -516,12 +518,10 @@ public class Departure implements Parcelable, Comparable<Departure> {
arrivalTimeOverride = in.readLong();
estimatedTripTime = in.readInt();
line = Line.values()[in.readInt()];
boolean[] bools = new boolean[4];
in.readBooleanArray(bools);
beganAsDeparted = bools[0];
bikeAllowed = bools[1];
requiresTransfer = bools[2];
transferScheduled = bools[3];
beganAsDeparted = in.readByte() == (byte) 1;
bikeAllowed = in.readByte() == (byte) 1;
requiresTransfer = in.readByte() == (byte) 1;
transferScheduled = in.readByte() == (byte) 1;
}
public static final Parcelable.Creator<Departure> CREATOR = new Parcelable.Creator<Departure>() {

View File

@ -10,8 +10,6 @@ public class Route {
private boolean requiresTransfer;
private Station transferStation;
private String direction;
private String fare;
private Long fareLastUpdated;
public Station getOrigin() {
return origin;
@ -69,22 +67,6 @@ public class Route {
this.direction = direction;
}
public String getFare() {
return fare;
}
public void setFare(String fare) {
this.fare = fare;
}
public Long getFareLastUpdated() {
return fareLastUpdated;
}
public void setFareLastUpdated(Long fareLastUpdated) {
this.fareLastUpdated = fareLastUpdated;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@ -100,10 +82,6 @@ public class Route {
builder.append(transferStation);
builder.append(", direction=");
builder.append(direction);
builder.append(", fare=");
builder.append(fare);
builder.append(", fareLastUpdated=");
builder.append(fareLastUpdated);
builder.append("]");
return builder.toString();
}

View File

@ -1,5 +1,11 @@
package com.dougkeen.bart.model;
import org.apache.commons.lang3.ObjectUtils;
import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.RoutesColumns;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@ -11,12 +17,39 @@ public class StationPair implements Parcelable {
this.destination = destination;
}
public StationPair(Long id, Station origin, Station destination) {
super();
this.origin = origin;
this.destination = destination;
this.id = id;
}
public StationPair(Parcel in) {
readFromParcel(in);
}
public static StationPair createFromCursor(Cursor cursor) {
StationPair pair = new StationPair(
Station.getByAbbreviation(CursorUtils.getString(cursor,
RoutesColumns.FROM_STATION)),
Station.getByAbbreviation(CursorUtils.getString(cursor,
RoutesColumns.TO_STATION)));
pair.id = CursorUtils.getLong(cursor, RoutesColumns._ID);
pair.fare = CursorUtils.getString(cursor, RoutesColumns.FARE);
pair.fareLastUpdated = CursorUtils.getLong(cursor,
RoutesColumns.FARE_LAST_UPDATED);
return pair;
}
private Long id;
private Station origin;
private Station destination;
private String fare;
private Long fareLastUpdated;
public Long getId() {
return id;
}
public Station getOrigin() {
return origin;
@ -26,6 +59,22 @@ public class StationPair implements Parcelable {
return destination;
}
public String getFare() {
return fare;
}
public void setFare(String fare) {
this.fare = fare;
}
public Long getFareLastUpdated() {
return fareLastUpdated;
}
public void setFareLastUpdated(Long fareLastUpdated) {
this.fareLastUpdated = fareLastUpdated;
}
public boolean isBetweenStations(Station station1, Station station2) {
return (origin.equals(station1) && destination.equals(station2))
|| (origin.equals(station2) && destination.equals(station1));
@ -51,6 +100,14 @@ public class StationPair implements Parcelable {
return result;
}
public boolean fareEquals(StationPair other) {
if (other == null)
return false;
return ObjectUtils.equals(getFare(), other.getFare())
&& ObjectUtils.equals(getFareLastUpdated(),
other.getFareLastUpdated());
}
@Override
public boolean equals(Object obj) {
if (this == obj)

View File

@ -1,4 +1,7 @@
package com.dougkeen.bart;
package com.dougkeen.bart.receivers;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.util.WakeLocker;
import android.content.BroadcastReceiver;
import android.content.Context;

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.services;
import java.util.ArrayList;
import java.util.Collections;
@ -21,6 +21,8 @@ import android.os.IBinder;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure;
@ -44,7 +46,8 @@ public class EtdService extends Service {
mServiceEngineMap = new HashMap<StationPair, EtdServiceEngine>();
}
public void registerListener(EtdServiceListener listener) {
public void registerListener(EtdServiceListener listener,
boolean limitToFirstNonDeparted) {
StationPair stationPair = getStationPairFromListener(listener);
if (stationPair == null)
return;
@ -53,7 +56,8 @@ public class EtdService extends Service {
mServiceEngineMap.put(stationPair,
new EtdServiceEngine(stationPair));
}
mServiceEngineMap.get(stationPair).registerListener(listener);
mServiceEngineMap.get(stationPair).registerListener(listener,
limitToFirstNonDeparted);
}
private StationPair getStationPairFromListener(EtdServiceListener listener) {
@ -116,6 +120,8 @@ public class EtdService extends Service {
// We'll only use the keys
private WeakHashMap<EtdServiceListener, Boolean> mListeners;
private boolean mLimitToFirstNonDeparted = true;
private List<Departure> mLatestDepartures;
private ScheduleInformation mLatestScheduleInfo;
@ -148,8 +154,11 @@ public class EtdService extends Service {
cursor.close();
}
protected void registerListener(EtdServiceListener listener) {
protected void registerListener(EtdServiceListener listener,
boolean limitToFirstNonDeparted) {
mListeners.put(listener, true);
if (!limitToFirstNonDeparted)
mLimitToFirstNonDeparted = false;
if (!mPendingEtdRequest) {
mStarted = true;
fetchLatestDepartures();
@ -439,6 +448,9 @@ public class EtdService extends Service {
if (departure.equals(boardedDeparture)) {
boardedDeparture.mergeEstimate(departure);
}
if (!departure.hasDeparted() && mLimitToFirstNonDeparted) {
break;
}
}
/*
@ -504,6 +516,10 @@ public class EtdService extends Service {
if (departure.equals(boardedDeparture)) {
boardedDeparture.mergeEstimate(departure);
}
if (!departure.hasDeparted() && mLimitToFirstNonDeparted) {
break;
}
}
}
Collections.sort(mLatestDepartures);

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.bart.services;
import java.util.List;
@ -18,17 +18,19 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log;
import android.util.TimeFormatException;
import com.dougkeen.bart.EtdService.EtdServiceBinder;
import com.dougkeen.bart.EtdService.EtdServiceListener;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener;
public class NotificationService extends Service implements EtdServiceListener {
@ -74,7 +76,7 @@ public class NotificationService extends Service implements EtdServiceListener {
public void onServiceConnected(ComponentName name, IBinder service) {
mEtdService = ((EtdServiceBinder) service).getService();
if (getStationPair() != null) {
mEtdService.registerListener(NotificationService.this);
mEtdService.registerListener(NotificationService.this, false);
}
mBound = true;
}
@ -142,7 +144,7 @@ public class NotificationService extends Service implements EtdServiceListener {
}
if (getStationPair() != null && mEtdService != null) {
mEtdService.registerListener(this);
mEtdService.registerListener(this, false);
}
Intent targetIntent = new Intent(Intent.ACTION_VIEW,
@ -163,7 +165,7 @@ public class NotificationService extends Service implements EtdServiceListener {
getStationPair().getUri());
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
alarmIntent.putExtra("departure", boardedDeparture);
alarmIntent.putExtra("departure", (Parcelable) boardedDeparture);
mAlarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
@ -317,7 +319,7 @@ public class NotificationService extends Service implements EtdServiceListener {
mStationPair.getOrigin().shortName + " to "
+ mStationPair.getDestination().shortName)
.setContentIntent(mNotificationIntent).setWhen(0);
if (android.os.Build.VERSION.SDK_INT > 16) {
if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder
.setPriority(NotificationCompat.PRIORITY_HIGH)
.addAction(

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart;
package com.dougkeen.util;
import android.content.Context;
import android.os.PowerManager;