Seems like it works

This commit is contained in:
dkeen 2011-05-23 11:59:34 -07:00
commit 434cc249ed
34 changed files with 1781 additions and 0 deletions

7
.classpath Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="output" path="bin"/>
</classpath>

3
.hgignore Normal file
View File

@ -0,0 +1,3 @@
syntax: glob
bin/*

33
.project Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>DontMissTheBart</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

50
AndroidManifest.xml Normal file
View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dougkeen.bart" android:versionCode="2"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="7" />
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
<activity android:name=".FavoritesDashboardActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/com.dougkeen.bart.favorite" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/com.dougkeen.bart.favorite" />
</intent-filter>
</activity>
<activity android:name=".AddFavoriteActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.INSERT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/com.dougkeen.bart.favorite" />
</intent-filter>
</activity>
<activity android:name=".ViewArrivalsActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/com.dougkeen.bart.favorite" />
</intent-filter>
</activity>
<provider android:name=".data.BartContentProvider"
android:authorities="com.dougkeen.bart.dataprovider" android:label="Don\'t Miss The Bart data provider" />
</application>
</manifest>

11
default.properties Normal file
View File

@ -0,0 +1,11 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-7

36
proguard.cfg Normal file
View File

@ -0,0 +1,36 @@
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

BIN
res/drawable-hdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
res/drawable-ldpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/drawable-mdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="15dp" android:width="60dp" />
</shape>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:paddingLeft="5dp" android:paddingRight="5dp">
<TextView android:id="@+id/form_header" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="24dp"
android:textStyle="bold" android:layout_alignParentTop="true"
android:text="@string/add_favorite_route" android:paddingBottom="10dp" />
<TextView android:id="@+id/origin_label" android:text="@string/origin"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="15dp" android:layout_below="@+id/form_header"
android:layout_alignLeft="@+id/form_header"></TextView>
<Spinner android:id="@+id/origin_spinner" android:layout_below="@id/origin_label"
android:layout_height="wrap_content" android:layout_width="fill_parent" />
<TextView android:id="@+id/destination_label" android:text="@string/destination"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="15dp" android:layout_below="@+id/origin_spinner"
android:layout_alignLeft="@+id/origin_spinner"></TextView>
<Spinner android:id="@+id/destination_spinner"
android:layout_below="@id/destination_label" android:layout_height="wrap_content"
android:layout_width="fill_parent" />
<LinearLayout android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_below="@id/destination_spinner"
style="@style/ButtonBar" android:id="@+id/buttonBar"
android:orientation="horizontal">
<Button android:text="@string/save" android:layout_weight="1"
android:layout_height="wrap_content" android:id="@+id/saveButton"
android:layout_width="fill_parent"></Button>
<Button android:text="@string/cancel" android:layout_weight="1"
android:layout_height="wrap_content" android:id="@+id/cancelButton"
android:layout_width="fill_parent"></Button>
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:id="@+id/destinationText" style="@style/ArrivalDestinationText"
android:layout_alignParentTop="true" android:layout_alignParentLeft="true" />
<ImageView android:id="@+id/destinationColorBar" android:src="@drawable/basic_rectangle"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/destinationText" />
<TextView android:layout_centerVertical="true"
android:layout_alignParentRight="true" android:id="@+id/countdown"
style="@style/ArrivalCountdownText" />
</RelativeLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:paddingLeft="5dp" android:paddingRight="5dp">
<TextView android:id="@+id/originText" style="@style/FavoriteListingTextView"
android:layout_alignParentTop="true" android:text="Origin" />
<TextView style="@style/FavoriteListingTextView" android:text="to"
android:layout_below="@id/originText" android:paddingLeft="15dp"
android:paddingRight="8dp" android:id="@+id/to" />
<TextView android:id="@+id/destinationText" style="@style/FavoriteListingTextView"
android:layout_below="@id/originText" android:layout_toRightOf="@id/to"
android:text="Destination" />
</RelativeLayout>

14
res/layout/main.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/listTitle" android:layout_width="fill_parent"
android:textSize="24dp" android:textStyle="bold"
android:layout_height="wrap_content" android:paddingLeft="5dp"
android:paddingRight="5dp" android:paddingBottom="10dp" />
<ListView android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_weight="1"
android:id="@android:id/list" />
<TextView android:id="@android:id/empty" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_weight="1" />
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:paddingTop="15dip" android:paddingBottom="15dip"
android:paddingLeft="5dip" android:paddingRight="5dip" android:id="@android:id/text1"
android:textColor="#cccccc" android:ellipsize="marquee"
android:singleLine="true">
</TextView>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/add_favorite" android:icon="@android:drawable/ic_menu_add"
android:id="@+id/add_favorite_menu_button"></item>
</menu>

4
res/values/colors.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
</resources>

18
res/values/strings.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Don\'t Miss The Bart</string>
<string name="favorite_routes">Favorite Routes</string>
<string name="empty_favorites_list_message">Press the menu button and select "Add
favorite" to
add a route</string>
<string name="add_favorite">Add favorite</string>
<string name="add_favorite_route">Add a favorite route</string>
<string name="origin">Origin</string>
<string name="destination">Destination</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="error_matching_origin_and_destination">The origin and destination stations must be different</string>
<string name="error_null_destination">You must select a destination station</string>
<string name="error_null_origin">You must select an origin station</string>
<string name="arrival_wait_message">Please wait while real time arrival data is loaded...</string>
</resources>

34
res/values/styles.xml Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme is the default theme. -->
<style name="Theme" parent="android:Theme"></style>
<style name="ButtonBar">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:paddingTop">10dp</item>
<item name="android:paddingBottom">10dp</item>
</style>
<style name="FavoriteListingTextView">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">20dp</item>
<item name="android:ellipsize">end</item>
<item name="android:singleLine">true</item>
</style>
<style name="ArrivalDestinationText">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">20dp</item>
<item name="android:singleLine">true</item>
</style>
<style name="ArrivalCountdownText">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">20dp</item>
<item name="android:singleLine">true</item>
</style>
</resources>

View File

@ -0,0 +1,92 @@
package com.dougkeen.bart;
import com.dougkeen.bart.data.FavoritesColumns;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.Toast;
public class AddFavoriteActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.add_favorite);
SpinnerAdapter originSpinnerAdapter = new ArrayAdapter<Station>(this,
R.layout.simple_spinner_item, Station.values());
((Spinner) findViewById(R.id.origin_spinner))
.setAdapter(originSpinnerAdapter);
SpinnerAdapter destinationSpinnerAdapter = new ArrayAdapter<Station>(
this,
R.layout.simple_spinner_item, Station.values());
((Spinner) findViewById(R.id.destination_spinner))
.setAdapter(destinationSpinnerAdapter);
Button saveButton = (Button) findViewById(R.id.saveButton);
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSaveButtonClick();
}
});
Button cancelButton = (Button) findViewById(R.id.cancelButton);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
}
protected void onSaveButtonClick() {
Station origin = (Station) ((Spinner) findViewById(R.id.origin_spinner))
.getSelectedItem();
Station destination = (Station) ((Spinner) findViewById(R.id.destination_spinner))
.getSelectedItem();
if (origin == null) {
Toast.makeText(this, com.dougkeen.bart.R.string.error_null_origin,
Toast.LENGTH_LONG);
return;
}
if (destination == null) {
Toast.makeText(this,
com.dougkeen.bart.R.string.error_null_destination,
Toast.LENGTH_LONG);
return;
}
if (origin.equals(destination)) {
Toast.makeText(
this,
com.dougkeen.bart.R.string.error_matching_origin_and_destination,
Toast.LENGTH_LONG);
return;
}
ContentValues values = new ContentValues();
values.put(FavoritesColumns.FROM_STATION.string, origin.abbreviation);
values.put(FavoritesColumns.TO_STATION.string, destination.abbreviation);
Uri newUri = getContentResolver().insert(
Constants.FAVORITE_CONTENT_URI, values);
setResult(RESULT_OK, (new Intent()).setAction(newUri.toString()));
finish();
}
}

View File

@ -0,0 +1,11 @@
package com.dougkeen.bart;
import android.net.Uri;
public class Constants {
public static final String AUTHORITY = "com.dougkeen.bart.dataprovider";
public static final String FAVORITE_CONTENT_TYPE = "vnd.android.cursor.dir/com.dougkeen.bart.favorite";
public static final String FAVORITE_CONTENT_ITEM_TYPE = "vnd.android.cursor.item/com.dougkeen.bart.favorite";
public static final Uri FAVORITE_CONTENT_URI = Uri.parse("content://"
+ AUTHORITY + "/favorites");
}

View File

@ -0,0 +1,95 @@
package com.dougkeen.bart;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.dougkeen.bart.data.Arrival;
import com.dougkeen.bart.data.RealTimeArrivals;
public class EtdContentHandler extends DefaultHandler {
public EtdContentHandler(Station origin, Station destination,
List<Route> routes) {
super();
realTimeArrivals = new RealTimeArrivals(origin, destination, routes);
}
private final static List<String> TAGS = Arrays.asList("date", "time",
"abbreviation", "minutes", "platform", "direction", "length",
"hexcolor", "bikeflag");
private RealTimeArrivals realTimeArrivals;
public RealTimeArrivals getRealTimeArrivals() {
return realTimeArrivals;
}
private String date;
private String currentDestination;
private String currentValue;
private Arrival currentArrival;
private boolean isParsingTag;
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (isParsingTag) {
currentValue = new String(ch, start, length);
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (TAGS.contains(localName)) {
isParsingTag = true;
}
if (localName.equals("estimate")) {
currentArrival = new Arrival();
currentArrival.setDestination(Station
.getByAbbreviation(currentDestination));
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (localName.equals("date")) {
date = currentValue;
} else if (localName.equals("time")) {
realTimeArrivals.setTime(Date.parse(date + " " + currentValue));
} else if (localName.equals("abbreviation")) {
currentDestination = currentValue;
} else if (localName.equals("minutes")) {
if (currentValue.equalsIgnoreCase("arrived")) {
currentArrival.setMinutes(0);
} else {
currentArrival.setMinutes(Integer.parseInt(currentValue));
}
} else if (localName.equals("platform")) {
currentArrival.setPlatform(currentValue);
} else if (localName.equals("direction")) {
currentArrival.setDirection(currentValue);
} else if (localName.equals("length")) {
currentArrival.setTrainLength(Integer.parseInt(currentValue));
} else if (localName.equals("hexcolor")) {
currentArrival.setDestinationColor("#ff"
+ currentValue.substring(1));
} else if (localName.equals("bikeflag")) {
currentArrival.setBikeAllowed(currentValue.equalsIgnoreCase("1"));
} else if (localName.equals("estimate")) {
realTimeArrivals.addArrival(currentArrival);
currentArrival = null;
} else if (localName.equals("etd")) {
currentDestination = null;
} else if (localName.equals("station")) {
realTimeArrivals.sortArrivals();
}
isParsingTag = false;
currentValue = null;
}
}

View File

@ -0,0 +1,83 @@
package com.dougkeen.bart;
import com.dougkeen.bart.data.FavoritesColumns;
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.SimpleCursorAdapter.ViewBinder;
import android.widget.TextView;
public class FavoritesDashboardActivity extends ListActivity {
protected Cursor mQuery;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((TextView) findViewById(R.id.listTitle))
.setText(R.string.favorite_routes);
((TextView) findViewById(android.R.id.empty))
.setText(R.string.empty_favorites_list_message);
mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] {
FavoritesColumns._ID.string,
FavoritesColumns.FROM_STATION.string,
FavoritesColumns.TO_STATION.string }, null, null,
FavoritesColumns._ID.string);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.favorite_listing,
mQuery,
new String[] { FavoritesColumns.FROM_STATION.string,
FavoritesColumns.TO_STATION.string },
new int[] { R.id.originText,
R.id.destinationText });
adapter.setViewBinder(new ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor,
int columnIndex) {
((TextView) view).setText(Station.getByAbbreviation(cursor
.getString(columnIndex)).name);
return true;
}
});
setListAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.favorites_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.add_favorite_menu_button) {
startActivity(new Intent(Intent.ACTION_INSERT,
Constants.FAVORITE_CONTENT_URI));
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
startActivity(new Intent(Intent.ACTION_VIEW,
ContentUris.withAppendedId(Constants.FAVORITE_CONTENT_URI, id)));
}
}

View File

@ -0,0 +1,109 @@
package com.dougkeen.bart;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import org.xml.sax.SAXException;
import com.dougkeen.bart.data.RealTimeArrivals;
import android.os.AsyncTask;
import android.util.Xml;
public abstract class GetRealTimeArrivalsTask extends
AsyncTask<GetRealTimeArrivalsTask.Params, Integer, RealTimeArrivals> {
private final static String API_KEY = "5LD9-IAYI-TRAT-MHHW";
private final static String API_URL = "http://api.bart.gov/api/etd.aspx?cmd=etd&key="
+ API_KEY + "&orig=%1$s&dir=%2$s";
private final static int MAX_ATTEMPTS = 3;
private IOException mIOException;
private List<Route> mRoutes;
@Override
protected RealTimeArrivals doInBackground(Params... paramsArray) {
// Always expect one param
Params params = paramsArray[0];
mRoutes = params.origin.getRoutesForDestination(params.destination);
URL sourceUrl;
try {
sourceUrl = new URL(String.format(API_URL,
params.origin.abbreviation, mRoutes.get(0).getDirection()));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
return getArrivalsFromNetwork(params, sourceUrl, 0);
}
private RealTimeArrivals getArrivalsFromNetwork(Params params,
URL sourceUrl, int attemptNumber) {
try {
EtdContentHandler handler = new EtdContentHandler(params.origin,
params.destination, mRoutes);
Xml.parse(sourceUrl.openStream(), Xml.findEncodingByName("UTF-8"),
handler);
final RealTimeArrivals realTimeArrivals = handler
.getRealTimeArrivals();
return realTimeArrivals;
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
if (attemptNumber < MAX_ATTEMPTS - 1) {
try {
Thread.sleep(5000);
} catch (InterruptedException interrupt) {
// Ignore... just go on to next attempt
}
return getArrivalsFromNetwork(params, sourceUrl,
attemptNumber++);
} else {
mIOException = e;
return null;
}
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
public static class Params {
public Params(Station origin, Station destination) {
super();
this.origin = origin;
this.destination = destination;
}
private Station origin;
private Station destination;
public Station getOrigin() {
return origin;
}
public Station getDestination() {
return destination;
}
}
@Override
protected void onPostExecute(RealTimeArrivals result) {
if (result != null) {
onResult(result);
} else {
onNetworkError(mIOException);
}
}
public abstract void onResult(RealTimeArrivals result);
public abstract void onNetworkError(IOException e);
}

View File

@ -0,0 +1,108 @@
package com.dougkeen.bart;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public enum Line {
RED(false, Station.MLBR, Station.SBRN, Station.SSAN, Station.COLM,
Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
Station.EMBR, Station.WOAK, Station._12TH, Station._19TH,
Station.MCAR, Station.ASHB, Station.DBRK, Station.NBRK,
Station.PLZA, Station.DELN, Station.RICH),
ORANGE(false, Station.FRMT, Station.UCTY, Station.SHAY,
Station.HAYW, Station.BAYF, Station.SANL, Station.COLS,
Station.FTVL, Station.LAKE, Station._12TH, Station._19TH,
Station.MCAR, Station.ASHB, Station.DBRK, Station.NBRK,
Station.PLZA, Station.DELN, Station.RICH),
YELLOW(false, Station.MLBR, Station.SFIA, Station.SBRN,
Station.SSAN, Station.COLM, Station.DALY, Station.BALB,
Station.GLEN, Station._24TH, Station._16TH, Station.CIVC,
Station.POWL, Station.MONT, Station.EMBR, Station.WOAK,
Station._12TH, Station._19TH, Station.MCAR, Station.ROCK,
Station.ORIN, Station.LAFY, Station.WCRK, Station.PHIL,
Station.CONC, Station.NCON),
BLUE(true, Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
Station.EMBR, Station.WOAK, Station.LAKE, Station.FTVL,
Station.COLS, Station.SANL, Station.BAYF, Station.CAST,
Station.WDUB, Station.DUBL),
GREEN(true, Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
Station.EMBR, Station.WOAK, Station.LAKE, Station.FTVL,
Station.COLS, Station.SANL, Station.BAYF, Station.HAYW,
Station.SHAY, Station.UCTY, Station.FRMT),
YELLOW_ORANGE_TRANSFER(YELLOW, ORANGE, Station.MLBR, Station.SFIA,
Station.SBRN, Station.SSAN, Station.COLM, Station.DALY,
Station.BALB, Station.GLEN, Station._24TH, Station._16TH,
Station.CIVC, Station.POWL, Station.MONT, Station.EMBR,
Station.WOAK, Station._12TH, Station._19TH,
Station.MCAR, Station.ASHB, Station.DBRK, Station.NBRK,
Station.PLZA, Station.DELN, Station.RICH);
public final List<Station> stations;
protected final boolean directionMayInvert;
protected final boolean requiresTransfer;
protected final Line transferLine1;
protected final Line transferLine2;
private Line(boolean directionMayInvert,
Station... stationArray) {
this.requiresTransfer = false;
this.directionMayInvert = directionMayInvert;
stations = Arrays.asList(stationArray);
this.transferLine1 = null;
this.transferLine2 = null;
}
private Line(Line transferLine1, Line transferLine2,
Station... stationArray) {
this.requiresTransfer = true;
this.directionMayInvert = false;
stations = Arrays.asList(stationArray);
this.transferLine1 = transferLine1;
this.transferLine2 = transferLine2;
}
public static Collection<Line> getLinesForStation(Station station) {
Collection<Line> lines = new ArrayList<Line>();
for (Line line : Line.values()) {
if (line.stations.contains(station)) {
lines.add(line);
}
}
return lines;
}
public static Set<Station> getPotentialDestinations(Station station) {
Set<Station> destinations = new TreeSet<Station>();
for (Line line : getLinesForStation(station)) {
destinations.addAll(line.stations);
}
destinations.remove(station);
return destinations;
}
public boolean trainDestinationIsApplicable(Station station) {
if (transferLine1 != null && transferLine1.stations.contains(station)) {
return true;
} else if (transferLine2 != null
&& transferLine2.stations.contains(station)) {
return true;
} else {
return stations.contains(station);
}
}
}

View File

@ -0,0 +1,66 @@
package com.dougkeen.bart;
public class Route {
private Station origin;
private Station destination;
private Line line;
private boolean requiresTransfer;
private String direction;
public Station getOrigin() {
return origin;
}
public void setOrigin(Station origin) {
this.origin = origin;
}
public Station getDestination() {
return destination;
}
public void setDestination(Station destination) {
this.destination = destination;
}
public Line getLine() {
return line;
}
public void setLine(Line line) {
this.line = line;
}
public boolean hasTransfer() {
return requiresTransfer;
}
public void setTransfer(boolean requiresTransfer) {
this.requiresTransfer = requiresTransfer;
}
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Route [origin=");
builder.append(origin);
builder.append(", destination=");
builder.append(destination);
builder.append(", line=");
builder.append(line);
builder.append(", requiresTransfer=");
builder.append(requiresTransfer);
builder.append(", direction=");
builder.append(direction);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,165 @@
package com.dougkeen.bart;
import java.util.ArrayList;
import java.util.List;
public enum Station {
_12TH("12th", "12th St./Oakland City Center", false, "bayf"),
_16TH("16th", "16th St. Mission", false),
_19TH("19th", "19th St./Oakland", false, "bayf"),
_24TH("24th", "24th St. Mission", false),
ASHB("ashb", "Ashby", false, "mcar"),
BALB("balb", "Balboa Park", false),
BAYF("bayf", "Bay Fair", true, "mcar"),
CAST("cast", "Castro Valley", false, "bayf"),
CIVC("civc", "Civic Center", false),
COLS("cols", "Coliseum/Oakland Airport", true, "mcar"),
COLM("colm", "Colma", false, "balb", "balb"),
CONC("conc", "Concord", false, "mcar"),
DALY("daly", "Daly City", false),
DBRK("dbrk", "Downtown Berkeley", false, "mcar"),
DUBL("dubl", "Dublin/Pleasanton", false, "bayf"),
DELN("deln", "El Cerrito del Norte", false, "mcar"),
PLZA("plza", "El Cerrito Plaza", false, "mcar"),
EMBR("embr", "Embarcadero", false),
FRMT("frmt", "Fremont", true, "bayf"),
FTVL("ftvl", "Fruitvale", true, "mcar"),
GLEN("glen", "Glen Park", false),
HAYW("hayw", "Hayward", true, "bayf"),
LAFY("lafy", "Lafayette", false, "mcar"),
LAKE("lake", "Lake Merritt", true, "mcar"),
MCAR("mcar", "MacArthur", false, "bayf"),
MLBR("mlbr", "Millbrae", false, "balb", "balb"),
MONT("mont", "Montgomery St.", false),
NBRK("nbrk", "North Berkeley", false, "mcar"),
NCON("ncon", "North Concord/Martinez", false, "mcar"),
ORIN("orin", "Orinda", false, "mcar"),
PITT("pitt", "Pittsburg/Bay Point", false, "mcar"),
PHIL("phil", "Pleasant Hill", false, "mcar"),
POWL("powl", "Powell St.", false),
RICH("rich", "Richmond", false, "mcar"),
ROCK("rock", "Rockridge", false, "mcar"),
SBRN("sbrn", "San Bruno", false, "balb", "balb"),
SANL("sanl", "San Leandro", true, "mcar"),
SFIA("sfia", "SFO Airport", false, "sbrn", "balb"),
SHAY("shay", "South Hayward", true, "bayf"),
SSAN("ssan", "South San Francisco", false, "balb", "balb"),
UCTY("ucty", "Union City", true, "bayf"),
WCRK("wcrk", "Walnut Creek", false, "mcar"),
WDUB("wdub", "West Dublin/Pleasanton", false, "bayf"),
WOAK("woak", "West Oakland", false);
public final String abbreviation;
public final String name;
public final boolean invertDirection;
protected final String inboundTransferStation;
protected final String outboundTransferStation;
private Station(String abbreviation, String name, boolean invertDirection) {
this.abbreviation = abbreviation;
this.name = name;
this.invertDirection = invertDirection;
this.inboundTransferStation = null;
this.outboundTransferStation = null;
}
private Station(String abbreviation, String name, boolean invertDirection,
String transferStation) {
this.abbreviation = abbreviation;
this.name = name;
this.invertDirection = invertDirection;
this.inboundTransferStation = transferStation;
this.outboundTransferStation = null;
}
private Station(String abbreviation, String name, boolean invertDirection,
String inboundTransferStation, String outboundTransferStation) {
this.abbreviation = abbreviation;
this.name = name;
this.invertDirection = invertDirection;
this.inboundTransferStation = inboundTransferStation;
this.outboundTransferStation = outboundTransferStation;
}
public static Station getByAbbreviation(String abbr) {
if (abbr == null) {
return null;
} else if (Character.isDigit(abbr.charAt(0))) {
return Station.valueOf("_" + abbr.toUpperCase());
} else {
return Station.valueOf(abbr.toUpperCase());
}
}
public Station getInboundTransferStation() {
return getByAbbreviation(inboundTransferStation);
}
public Station getOutboundTransferStation() {
return getByAbbreviation(outboundTransferStation);
}
public boolean isValidEndpointForDestination(Station dest, Station endpoint) {
for (Line line : Line.values()) {
int origIndex = line.stations.indexOf(this);
if (origIndex < 0)
continue;
int destIndex = line.stations.indexOf(dest);
if (destIndex < 0)
continue;
int endpointIndex = line.stations.indexOf(endpoint);
if (endpointIndex >= 0)
return true;
}
return false;
}
public List<Route> getRoutesForDestination(Station dest) {
return getRoutesForDestination(dest, false);
}
public List<Route> getRoutesForDestination(Station dest, boolean isTransfer) {
if (dest == null)
return null;
Boolean isNorth = null;
List<Route> returnList = new ArrayList<Route>();
for (Line line : Line.values()) {
int origIndex = line.stations.indexOf(this);
if (origIndex < 0)
continue;
int destIndex = line.stations.indexOf(dest);
if (destIndex < 0)
continue;
isNorth = (origIndex < destIndex);
if (line.directionMayInvert && this.invertDirection) {
isNorth = !isNorth;
}
Route route = new Route();
route.setOrigin(this);
route.setDestination(dest);
route.setDirection(isNorth ? "n" : "s");
route.setLine(line);
if (isTransfer || line.requiresTransfer) {
route.setTransfer(true);
} else {
route.setTransfer(false);
}
returnList.add(route);
}
if (isNorth == null) {
if (outboundTransferStation != null) {
returnList.addAll(getOutboundTransferStation()
.getRoutesForDestination(dest, true));
} else {
returnList.addAll(getRoutesForDestination(dest
.getInboundTransferStation(), true));
}
}
return returnList;
}
public String toString() {
return name;
}
}

View File

@ -0,0 +1,195 @@
package com.dougkeen.bart;
import java.io.IOException;
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.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.dougkeen.bart.GetRealTimeArrivalsTask.Params;
import com.dougkeen.bart.data.Arrival;
import com.dougkeen.bart.data.FavoritesColumns;
import com.dougkeen.bart.data.RealTimeArrivals;
public class ViewArrivalsActivity extends ListActivity {
private Uri mUri;
private Station mOrigin;
private Station mDestination;
private ArrayAdapter<Arrival> mArrivalsAdapter;
private TextView mListTitleView;
private AsyncTask<Params, Integer, RealTimeArrivals> mGetArrivalsTask;
private boolean mIsAutoUpdating = false;
private final Runnable AUTO_UPDATE_RUNNABLE = new Runnable() {
@Override
public void run() {
runAutoUpdate();
}
};
private PowerManager.WakeLock mWakeLock;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
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[] {
FavoritesColumns.FROM_STATION.string,
FavoritesColumns.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 = mOrigin.name + " to " + mDestination.name;
mListTitleView = (TextView) findViewById(R.id.listTitle);
mListTitleView.setText(header);
((TextView) findViewById(android.R.id.empty))
.setText(R.string.arrival_wait_message);
mArrivalsAdapter = new ArrayAdapter<Arrival>(
this, R.layout.simple_spinner_item);
setListAdapter(mArrivalsAdapter);
fetchLatestArrivals();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
PowerManager powerManaer = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManaer.newWakeLock(
PowerManager.SCREEN_DIM_WAKE_LOCK, "ViewArrivalsActivity");
mWakeLock.acquire();
} else if (mWakeLock != null) {
mWakeLock.release();
}
}
private void fetchLatestArrivals() {
mGetArrivalsTask = new GetRealTimeArrivalsTask() {
@Override
public void onResult(RealTimeArrivals result) {
processLatestArrivals(result);
}
@Override
public void onNetworkError(IOException e) {
Toast.makeText(ViewArrivalsActivity.this, e.getMessage(),
Toast.LENGTH_SHORT).show();
}
};
mGetArrivalsTask.execute(new GetRealTimeArrivalsTask.Params(mOrigin,
mDestination));
}
protected void processLatestArrivals(RealTimeArrivals result) {
Arrival firstArrival = null;
final List<Arrival> arrivals = result.getArrivals();
if (mArrivalsAdapter.getCount() > 0) {
int adapterIndex = -1;
for (Arrival arrival : arrivals) {
adapterIndex++;
Arrival existingArrival = null;
if (adapterIndex < mArrivalsAdapter.getCount()) {
existingArrival = mArrivalsAdapter.getItem(adapterIndex);
}
while (existingArrival != null
&& !arrival.equals(existingArrival)) {
mArrivalsAdapter.remove(existingArrival);
if (adapterIndex < mArrivalsAdapter.getCount()) {
existingArrival = mArrivalsAdapter
.getItem(adapterIndex);
} else {
existingArrival = null;
}
}
if (existingArrival != null) {
existingArrival.mergeEstimate(arrival);
} else {
mArrivalsAdapter.add(arrival);
existingArrival = arrival;
}
if (firstArrival == null) {
firstArrival = existingArrival;
}
}
} else {
for (Arrival arrival : arrivals) {
if (firstArrival == null) {
firstArrival = arrival;
}
mArrivalsAdapter.add(arrival);
}
}
mArrivalsAdapter.notifyDataSetChanged();
if (hasWindowFocus() && firstArrival != null) {
if (firstArrival.getUncertaintySeconds() > 17
|| firstArrival.getMinutes() == 0) {
// Get more data in 20s
mListTitleView.postDelayed(new Runnable() {
@Override
public void run() {
fetchLatestArrivals();
}
}, 20000);
} else {
// Get more when next train arrives
mListTitleView.postDelayed(new Runnable() {
@Override
public void run() {
fetchLatestArrivals();
}
}, firstArrival.getMinSecondsLeft() * 1000);
}
if (!mIsAutoUpdating) {
mIsAutoUpdating = true;
runAutoUpdate();
}
} else {
mIsAutoUpdating = false;
}
}
private void runAutoUpdate() {
if (mIsAutoUpdating) {
mArrivalsAdapter.notifyDataSetChanged();
}
if (hasWindowFocus()) {
mListTitleView.postDelayed(AUTO_UPDATE_RUNNABLE, 1000);
} else {
mIsAutoUpdating = false;
}
}
}

View File

@ -0,0 +1,219 @@
package com.dougkeen.bart.data;
import com.dougkeen.bart.Station;
public class Arrival implements Comparable<Arrival> {
public Arrival() {
super();
}
public Arrival(String destinationAbbr, String destinationColor,
String platform, String direction, boolean bikeAllowed,
int trainLength, int minutes) {
super();
this.destination = Station.getByAbbreviation(destinationAbbr);
this.destinationColor = destinationColor;
this.platform = platform;
this.direction = direction;
this.bikeAllowed = bikeAllowed;
this.trainLength = trainLength;
this.minutes = minutes;
}
private Station destination;
private String destinationColor;
private String platform;
private String direction;
private boolean bikeAllowed;
private int trainLength;
private boolean requiresTransfer;
private int minutes;
private long minEstimate;
private long maxEstimate;
public Station getDestination() {
return destination;
}
public void setDestination(Station destination) {
this.destination = destination;
}
public String getDestinationName() {
if (destination != null)
return destination.name;
return null;
}
public String getDestinationAbbreviation() {
if (destination != null)
return destination.abbreviation;
return null;
}
public String getDestinationColor() {
return destinationColor;
}
public void setDestinationColor(String destinationColor) {
this.destinationColor = destinationColor;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public boolean isBikeAllowed() {
return bikeAllowed;
}
public void setBikeAllowed(boolean bikeAllowed) {
this.bikeAllowed = bikeAllowed;
}
public int getTrainLength() {
return trainLength;
}
public void setTrainLength(int trainLength) {
this.trainLength = trainLength;
}
public boolean getRequiresTransfer() {
return requiresTransfer;
}
public void setRequiresTransfer(boolean requiresTransfer) {
this.requiresTransfer = requiresTransfer;
}
public int getMinutes() {
return minutes;
}
public void setMinutes(int minutes) {
this.minutes = minutes;
}
public long getMinEstimate() {
return minEstimate;
}
public void setMinEstimate(long minEstimate) {
this.minEstimate = minEstimate;
}
public long getMaxEstimate() {
return maxEstimate;
}
public void setMaxEstimate(long maxEstimate) {
this.maxEstimate = maxEstimate;
}
public int getUncertaintySeconds() {
return (int) (maxEstimate - minEstimate + 1000) / 2000;
}
public int getMinSecondsLeft() {
return (int) ((getMinEstimate() - System.currentTimeMillis()) / 1000);
}
public int getMaxSecondsLeft() {
return (int) ((getMaxEstimate() - System.currentTimeMillis()) / 1000);
}
public int getMeanSecondsLeft() {
return (int) (((getMinEstimate() + getMaxEstimate()) / 2 - System
.currentTimeMillis()) / 1000);
}
public void calculateEstimates(long originalEstimateTime) {
setMinEstimate(originalEstimateTime + (getMinutes() * 60 * 1000));
setMaxEstimate(getMinEstimate() + (59 * 1000));
}
public void mergeEstimate(Arrival arrival) {
setMinEstimate(Math.max(getMinEstimate(), arrival.getMinEstimate()));
setMaxEstimate(Math.min(getMaxEstimate(), arrival.getMaxEstimate()));
}
@Override
public int compareTo(Arrival another) {
return (this.getMinutes() > another.getMinutes()) ? 1 : (
(this.getMinutes() == another.getMinutes()) ? 0 : -1);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Arrival other = (Arrival) obj;
if (bikeAllowed != other.bikeAllowed)
return false;
if (destination != other.destination)
return false;
if (destinationColor == null) {
if (other.destinationColor != null)
return false;
} else if (!destinationColor.equals(other.destinationColor))
return false;
if (direction == null) {
if (other.direction != null)
return false;
} else if (!direction.equals(other.direction))
return false;
if (platform == null) {
if (other.platform != null)
return false;
} else if (!platform.equals(other.platform))
return false;
if (trainLength != other.trainLength)
return false;
long delta = (getMinEstimate() - other.getMinEstimate());
return delta > -60000 && delta < 60000;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(destination);
if(requiresTransfer) {
builder.append(" (w/ xfer)");
}
builder.append(", ");
builder.append(trainLength);
int secondsLeft = getMeanSecondsLeft();
if (getMinutes() == 0 || secondsLeft < 0) {
builder.append(" car train has arrived");
} else {
builder.append(" car train in ");
builder.append(secondsLeft / 60);
builder.append("m, ");
builder.append(secondsLeft % 60);
builder.append("s, ±");
builder.append(getUncertaintySeconds());
builder.append("s");
}
return builder.toString();
}
}

View File

@ -0,0 +1,188 @@
package com.dougkeen.bart.data;
import java.util.HashMap;
import com.dougkeen.bart.Constants;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class BartContentProvider extends ContentProvider {
private static final UriMatcher sUriMatcher;
private static HashMap<String, String> sFavoritesProjectionMap;
private static final int FAVORITES = 1;
private static final int FAVORITE_ID = 2;
/**
* The default sort order for events
*/
private static final String DEFAULT_SORT_ORDER = FavoritesColumns.FROM_STATION.string
+ " DESC";
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(Constants.AUTHORITY, "favorites", FAVORITES);
sUriMatcher.addURI(Constants.AUTHORITY, "favorites/#", FAVORITE_ID);
sFavoritesProjectionMap = new HashMap<String, String>();
sFavoritesProjectionMap.put(FavoritesColumns._ID.string,
FavoritesColumns._ID.string);
sFavoritesProjectionMap.put(FavoritesColumns.FROM_STATION.string,
FavoritesColumns.FROM_STATION.string);
sFavoritesProjectionMap.put(FavoritesColumns.TO_STATION.string,
FavoritesColumns.TO_STATION.string);
}
private DatabaseHelper mDatabaseHelper;
@Override
public boolean onCreate() {
mDatabaseHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
return Constants.FAVORITE_CONTENT_TYPE;
} else if (match == FAVORITE_ID) {
return Constants.FAVORITE_CONTENT_ITEM_TYPE;
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs,
String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
String orderBy = sortOrder;
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
qb.setTables(DatabaseHelper.FAVORITES_TABLE_NAME);
qb.setProjectionMap(sFavoritesProjectionMap);
} else if (match == FAVORITE_ID) {
qb.setTables(DatabaseHelper.FAVORITES_TABLE_NAME);
qb.setProjectionMap(sFavoritesProjectionMap);
qb.appendWhere(FavoritesColumns._ID + " = "
+ uri.getPathSegments().get(1));
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// If no sort order is specified use the default
if (TextUtils.isEmpty(orderBy)) {
orderBy = DEFAULT_SORT_ORDER;
}
// Get the database and run the query
Cursor cursor = qb.query(db, projection, selection, selectionArgs,
null, null, orderBy);
// Tell the cursor what uri to watch, so it knows when its source data
// changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
// TODO: Hook this up to the REST service?
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
// Validate the requested uri
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
long rowId = -1;
Cursor cursor = db
.query(DatabaseHelper.FAVORITES_TABLE_NAME,
new String[] { FavoritesColumns._ID.string },
FavoritesColumns.FROM_STATION + "=? AND "
+ FavoritesColumns.TO_STATION + "=?",
new String[] {
values.getAsString(FavoritesColumns.FROM_STATION.string),
values.getAsString(FavoritesColumns.TO_STATION.string) },
null,
null,
null);
try {
if (cursor.moveToFirst()) {
rowId = cursor.getLong(0);
}
} finally {
CursorUtils.closeCursorQuietly(cursor);
}
if (rowId < 0) {
rowId = db.insert(DatabaseHelper.FAVORITES_TABLE_NAME,
FavoritesColumns.FROM_STATION.string, values);
}
if (rowId > 0) {
Uri eventUri = ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(eventUri, null,
false);
return eventUri;
}
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
return 0;
// No updating supported yet
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
// TODO: Sync with REST service?
SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
int count;
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
count = db.delete(DatabaseHelper.FAVORITES_TABLE_NAME, where,
whereArgs);
} else if (match == FAVORITE_ID) {
String favoriteId = uri.getPathSegments().get(1);
count = db.delete(DatabaseHelper.FAVORITES_TABLE_NAME,
FavoritesColumns._ID + " = "
+ favoriteId
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}

View File

@ -0,0 +1,15 @@
package com.dougkeen.bart.data;
import android.database.Cursor;
public final class CursorUtils {
private CursorUtils() {
// Static only class
}
public static final void closeCursorQuietly(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
}

View File

@ -0,0 +1,29 @@
package com.dougkeen.bart.data;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "bart.dougkeen.db";
private static final int DATABASE_VERSION = 1;
public static final String FAVORITES_TABLE_NAME = "Favorites";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + FAVORITES_TABLE_NAME + " (" +
FavoritesColumns._ID.getColumnDef() + " PRIMARY KEY, " +
FavoritesColumns.FROM_STATION.getColumnDef() + ", " +
FavoritesColumns.TO_STATION.getColumnDef() + ");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

View File

@ -0,0 +1,20 @@
package com.dougkeen.bart.data;
public enum FavoritesColumns {
_ID("_id", "INTEGER"),
FROM_STATION("FROM_STATION", "TEXT"),
TO_STATION("TO_STATION", "TEXT");
// This class cannot be instantiated
private FavoritesColumns(String string, String type) {
this.string = string;
this.sqliteType = type;
}
public final String string;
public final String sqliteType;
protected String getColumnDef() {
return string + " " + sqliteType;
}
}

View File

@ -0,0 +1,100 @@
package com.dougkeen.bart.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.dougkeen.bart.Route;
import com.dougkeen.bart.Station;
public class RealTimeArrivals {
public RealTimeArrivals(Station origin, Station destination,
List<Route> routes) {
this.origin = origin;
this.destination = destination;
this.routes = routes;
}
private Station origin;
private Station destination;
private long time;
private List<Arrival> arrivals;
private List<Route> routes;
public Station getOrigin() {
return origin;
}
public void setOrigin(Station origin) {
this.origin = origin;
}
public Station getDestination() {
return destination;
}
public void setDestination(Station destination) {
this.destination = destination;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public List<Arrival> getArrivals() {
if (arrivals == null) {
arrivals = new ArrayList<Arrival>();
}
return arrivals;
}
public void setArrivals(List<Arrival> arrivals) {
this.arrivals = arrivals;
}
public void addArrival(Arrival arrival) {
Station destination = Station.getByAbbreviation(arrival
.getDestinationAbbreviation());
for (Route route : routes) {
if (route.getLine().trainDestinationIsApplicable(destination)) {
arrival.setRequiresTransfer(route.hasTransfer());
getArrivals().add(arrival);
arrival.calculateEstimates(time);
return;
}
}
}
public void sortArrivals() {
Collections.sort(getArrivals());
}
public List<Route> getRoutes() {
return routes;
}
public void setRoutes(List<Route> routes) {
this.routes = routes;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("RealTimeArrivals [origin=");
builder.append(origin);
builder.append(", destination=");
builder.append(destination);
builder.append(", time=");
builder.append(time);
builder.append(", arrivals=");
builder.append(arrivals);
builder.append("]");
return builder.toString();
}
}