Reorganize the your_train and departure_listing layouts (and corresponding custom views) for easier editing, greater flexibility for different screens, and to fix a layout bug that was introduced when updating the libraries where the "Your Train" section would be too wide.

General code cleanup, warnings fixes, and readability improvements.
This commit is contained in:
Danny Weinberg 2015-08-12 07:41:16 -07:00
parent 4ee80b3f90
commit 2481f0c229
13 changed files with 424 additions and 350 deletions

View File

@ -17,6 +17,7 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.Vibrator; import android.os.Vibrator;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode; import android.support.v7.view.ActionMode;
import android.text.format.DateFormat; import android.text.format.DateFormat;
@ -48,6 +49,7 @@ import com.dougkeen.bart.services.EtdService;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder; import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener; import com.dougkeen.bart.services.EtdService.EtdServiceListener;
import com.dougkeen.bart.services.EtdService_; import com.dougkeen.bart.services.EtdService_;
import com.dougkeen.util.Assert;
import com.dougkeen.util.Observer; import com.dougkeen.util.Observer;
import com.dougkeen.util.WakeLocker; import com.dougkeen.util.WakeLocker;
@ -86,8 +88,7 @@ public class ViewDeparturesActivity extends AppCompatActivity implements
mProgress = (ProgressBar) findViewById(android.R.id.progress); mProgress = (ProgressBar) findViewById(android.R.id.progress);
mDeparturesAdapter = new DepartureArrayAdapter(this, mDeparturesAdapter = new DepartureArrayAdapter(this);
R.layout.departure_listing);
setListAdapter(mDeparturesAdapter); setListAdapter(mDeparturesAdapter);
final ListView listView = getListView(); final ListView listView = getListView();
@ -127,9 +128,9 @@ public class ViewDeparturesActivity extends AppCompatActivity implements
} }
if (savedInstanceState != null) { if (savedInstanceState != null) {
if (savedInstanceState.containsKey("departures")) { Parcelable[] departuresArray = savedInstanceState.getParcelableArray("departures");
for (Parcelable departure : savedInstanceState if (departuresArray != null) {
.getParcelableArray("departures")) { for (Parcelable departure : departuresArray) {
mDeparturesAdapter.add((Departure) departure); mDeparturesAdapter.add((Departure) departure);
} }
mDeparturesAdapter.notifyDataSetChanged(); mDeparturesAdapter.notifyDataSetChanged();
@ -151,8 +152,9 @@ public class ViewDeparturesActivity extends AppCompatActivity implements
} }
refreshBoardedDeparture(false); refreshBoardedDeparture(false);
getSupportActionBar().setHomeButtonEnabled(true); ActionBar supportActionBar = Assert.notNull(getSupportActionBar());
getSupportActionBar().setDisplayHomeAsUpEnabled(true); supportActionBar.setHomeButtonEnabled(true);
supportActionBar.setDisplayHomeAsUpEnabled(true);
if (bartRunnerApplication.shouldPlayAlarmRingtone()) { if (bartRunnerApplication.shouldPlayAlarmRingtone()) {
soundTheAlarm(); soundTheAlarm();
@ -440,7 +442,7 @@ public class ViewDeparturesActivity extends AppCompatActivity implements
return; return;
} }
mYourTrainSection.updateFromBoardedDeparture(); mYourTrainSection.updateFromBoardedDeparture(boardedDeparture);
if (currentVisibility != View.VISIBLE) { if (currentVisibility != View.VISIBLE) {
showYourTrainSection(animate); showYourTrainSection(animate);

View File

@ -0,0 +1,46 @@
package com.dougkeen.bart.controls;
import android.content.Context;
import android.support.v7.widget.LinearLayoutCompat;
import android.util.AttributeSet;
import android.widget.Checkable;
import com.dougkeen.bart.R;
/**
* A {@link android.widget.LinearLayout} that implements {@link Checkable} and changes
* its background color when checked.
*/
public class CheckableLinearLayout extends LinearLayoutCompat implements Checkable {
private boolean mChecked;
public CheckableLinearLayout(Context context) {
super(context);
}
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public void setChecked(boolean checked) {
mChecked = checked;
int colorRes = isChecked() ? R.color.blue_selection : android.R.color.transparent;
setBackgroundResource(colorRes);
}
@Override
public void toggle() {
setChecked(!isChecked());
}
}

View File

@ -1,42 +0,0 @@
package com.dougkeen.bart.controls;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.Checkable;
import android.widget.RelativeLayout;
import com.dougkeen.bart.R;
public class DepartureListItemLayout extends RelativeLayout implements
Checkable {
public DepartureListItemLayout(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.departure_listing, this,
true);
}
private boolean mChecked;
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public void setChecked(boolean checked) {
mChecked = checked;
if (isChecked()) {
setBackgroundDrawable(getContext().getResources().getDrawable(
R.color.blue_selection));
} else {
setBackgroundDrawable(getContext().getResources().getDrawable(
android.R.color.transparent));
}
}
@Override
public void toggle() {
setChecked(!isChecked());
}
}

View File

@ -10,8 +10,7 @@ import android.widget.TextSwitcher;
import com.dougkeen.bart.R; import com.dougkeen.bart.R;
import com.dougkeen.bart.model.TextProvider; import com.dougkeen.bart.model.TextProvider;
public class TimedTextSwitcher extends TextSwitcher implements public class TimedTextSwitcher extends TextSwitcher implements Ticker.TickSubscriber {
Ticker.TickSubscriber {
public TimedTextSwitcher(Context context, AttributeSet attrs) { public TimedTextSwitcher(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);

View File

@ -2,41 +2,51 @@ package com.dougkeen.bart.controls;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Checkable; import android.widget.Checkable;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R; import com.dougkeen.bart.R;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.TextProvider; import com.dougkeen.bart.model.TextProvider;
import com.dougkeen.util.Observer; import com.dougkeen.util.Observer;
public class YourTrainLayout extends RelativeLayout implements Checkable { public class YourTrainLayout extends FrameLayout implements Checkable {
private final TextView destinationText;
private final TextView trainLength;
private final View colorBar;
private final ImageView bikeIcon;
private final View xferIcon;
private final CountdownTextView departureCountdown;
private final CountdownTextView arrivalCountdown;
private final TextView alarmText;
public YourTrainLayout(Context context) { public YourTrainLayout(Context context) {
super(context); this(context, null);
assignLayout(context); }
public YourTrainLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} }
public YourTrainLayout(Context context, AttributeSet attrs, int defStyle) { public YourTrainLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
assignLayout(context);
}
public YourTrainLayout(Context context, AttributeSet attrs) {
super(context, attrs);
assignLayout(context);
}
public void assignLayout(Context context) {
LayoutInflater.from(context).inflate(R.layout.your_train, this, true); LayoutInflater.from(context).inflate(R.layout.your_train, this, true);
destinationText = (TextView) findViewById(R.id.yourTrainDestinationText);
trainLength = (TextView) findViewById(R.id.yourTrainTrainLengthText);
colorBar = findViewById(R.id.yourTrainDestinationColorBar);
bikeIcon = (ImageView) findViewById(R.id.yourTrainBikeIcon);
xferIcon = findViewById(R.id.yourTrainXferIcon);
departureCountdown = (CountdownTextView) findViewById(R.id.yourTrainDepartureCountdown);
arrivalCountdown = (CountdownTextView) findViewById(R.id.yourTrainArrivalCountdown);
alarmText = (TextView) findViewById(R.id.alarmText);
} }
private boolean mChecked; private boolean mChecked;
@ -75,13 +85,8 @@ public class YourTrainLayout extends RelativeLayout implements Checkable {
} }
private void setBackground() { private void setBackground() {
if (isChecked()) { int colorRes = isChecked() ? R.color.blue_selection : R.color.gray;
setBackgroundDrawable(getContext().getResources().getDrawable( setBackgroundResource(colorRes);
R.color.blue_selection));
} else {
setBackgroundDrawable(getContext().getResources().getDrawable(
R.color.gray));
}
} }
@Override @Override
@ -89,11 +94,10 @@ public class YourTrainLayout extends RelativeLayout implements Checkable {
setChecked(!isChecked()); setChecked(!isChecked());
} }
public void updateFromBoardedDeparture() { public void updateFromBoardedDeparture(final Departure boardedDeparture) {
final Departure boardedDeparture = ((BartRunnerApplication) ((Activity) getContext()) if (boardedDeparture == null) {
.getApplication()).getBoardedDeparture();
if (boardedDeparture == null)
return; return;
}
if (!boardedDeparture.equals(mDeparture)) { if (!boardedDeparture.equals(mDeparture)) {
if (mDeparture != null) { if (mDeparture != null) {
@ -110,36 +114,23 @@ public class YourTrainLayout extends RelativeLayout implements Checkable {
mDeparture = boardedDeparture; mDeparture = boardedDeparture;
((TextView) findViewById(R.id.yourTrainDestinationText)) destinationText.setText(boardedDeparture.getTrainDestination().toString());
.setText(boardedDeparture.getTrainDestination().toString()); trainLength.setText(boardedDeparture.getTrainLengthAndPlatform());
((TextView) findViewById(R.id.yourTrainTrainLengthText)) colorBar.setBackgroundColor(boardedDeparture.getTrainDestinationColor());
.setText(boardedDeparture.getTrainLengthAndPlatform());
ImageView colorBar = (ImageView) findViewById(R.id.yourTrainDestinationColorBar);
((GradientDrawable) colorBar.getDrawable()).setColor(Color
.parseColor(boardedDeparture.getTrainDestinationColor()));
ImageView bikeIcon = (ImageView) findViewById(R.id.yourTrainBikeIcon);
if (boardedDeparture.isBikeAllowed()) { if (boardedDeparture.isBikeAllowed()) {
bikeIcon.setImageDrawable(getResources().getDrawable( bikeIcon.setImageResource(R.drawable.bike);
R.drawable.bike));
} else { } else {
bikeIcon.setImageDrawable(getResources().getDrawable( bikeIcon.setImageResource(R.drawable.nobike);
R.drawable.nobike));
} }
if (boardedDeparture.getRequiresTransfer()) { if (boardedDeparture.getRequiresTransfer()) {
findViewById(R.id.yourTrainXferIcon) xferIcon.setVisibility(View.VISIBLE);
.setVisibility(View.VISIBLE);
} else { } else {
findViewById(R.id.yourTrainXferIcon) xferIcon.setVisibility(View.INVISIBLE);
.setVisibility(View.INVISIBLE);
} }
updateAlarmIndicator(); updateAlarmIndicator();
CountdownTextView departureCountdown = (CountdownTextView) findViewById(R.id.yourTrainDepartureCountdown);
CountdownTextView arrivalCountdown = (CountdownTextView) findViewById(R.id.yourTrainArrivalCountdown);
final TextProvider textProvider = new TextProvider() { final TextProvider textProvider = new TextProvider() {
@Override @Override
public String getText(long tickNumber) { public String getText(long tickNumber) {
@ -169,11 +160,10 @@ public class YourTrainLayout extends RelativeLayout implements Checkable {
private void updateAlarmIndicator() { private void updateAlarmIndicator() {
if (!mDeparture.isAlarmPending()) { if (!mDeparture.isAlarmPending()) {
findViewById(R.id.alarmText).setVisibility(GONE); alarmText.setVisibility(GONE);
} else { } else {
findViewById(R.id.alarmText).setVisibility(VISIBLE); alarmText.setVisibility(VISIBLE);
((TextView) findViewById(R.id.alarmText)).setText(String alarmText.setText(String.valueOf(mDeparture.getAlarmLeadTimeMinutes()));
.valueOf(mDeparture.getAlarmLeadTimeMinutes()));
} }
} }

View File

@ -1,13 +1,8 @@
package com.dougkeen.bart.data; package com.dougkeen.bart.data;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -21,65 +16,36 @@ import android.widget.ViewSwitcher.ViewFactory;
import com.dougkeen.bart.R; import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.CountdownTextView; import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.controls.DepartureListItemLayout; import com.dougkeen.bart.controls.CheckableLinearLayout;
import com.dougkeen.bart.controls.TimedTextSwitcher; import com.dougkeen.bart.controls.TimedTextSwitcher;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.TextProvider; import com.dougkeen.bart.model.TextProvider;
import org.apache.commons.lang3.StringUtils;
public class DepartureArrayAdapter extends ArrayAdapter<Departure> { public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
private Drawable noBikeDrawable; private Drawable noBikeDrawable;
private Drawable bikeDrawable; private Drawable bikeDrawable;
public DepartureArrayAdapter(Context context, int textViewResourceId, @SuppressWarnings("deprecation")
Departure[] objects) { public DepartureArrayAdapter(Context context) {
super(context, textViewResourceId, objects); super(context, 0 /* resource, unused since we override getView */);
assignBikeDrawables(); Resources resources = context.getResources();
} // We need to use the deprecated getDrawable since the newer version that
// replaces it was only made available in API 21.
private void assignBikeDrawables() { noBikeDrawable = resources.getDrawable(R.drawable.nobike);
noBikeDrawable = getContext().getResources().getDrawable( bikeDrawable = resources.getDrawable(R.drawable.bike);
R.drawable.nobike);
bikeDrawable = getContext().getResources().getDrawable(R.drawable.bike);
}
public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId, Departure[] objects) {
super(context, resource, textViewResourceId, objects);
assignBikeDrawables();
}
public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId, List<Departure> objects) {
super(context, resource, textViewResourceId, objects);
assignBikeDrawables();
}
public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId) {
super(context, resource, textViewResourceId);
assignBikeDrawables();
}
public DepartureArrayAdapter(Context context, int textViewResourceId,
List<Departure> objects) {
super(context, textViewResourceId, objects);
assignBikeDrawables();
}
public DepartureArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
assignBikeDrawables();
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
View view; View view;
if (convertView != null if (convertView != null && convertView instanceof CheckableLinearLayout) {
&& convertView instanceof DepartureListItemLayout) {
view = convertView; view = convertView;
} else { } else {
view = new DepartureListItemLayout(getContext()); view = LayoutInflater.from(getContext()).inflate(
R.layout.departure_listing, parent, false /* attachToRoot */);
} }
final Departure departure = getItem(position); final Departure departure = getItem(position);
@ -132,10 +98,8 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
}); });
} }
ImageView colorBar = (ImageView) view view.findViewById(R.id.destinationColorBar)
.findViewById(R.id.destinationColorBar); .setBackgroundColor(departure.getTrainDestinationColor());
((GradientDrawable) colorBar.getDrawable()).setColor(Color
.parseColor(departure.getTrainDestinationColor()));
CountdownTextView countdownTextView = (CountdownTextView) view CountdownTextView countdownTextView = (CountdownTextView) view
.findViewById(R.id.countdown); .findViewById(R.id.countdown);
countdownTextView.setText(departure.getCountdownText()); countdownTextView.setText(departure.getCountdownText());
@ -179,11 +143,9 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
bikeIcon.setImageDrawable(noBikeDrawable); bikeIcon.setImageDrawable(noBikeDrawable);
} }
if (departure.getRequiresTransfer()) { if (departure.getRequiresTransfer()) {
view.findViewById(R.id.xferIcon) view.findViewById(R.id.xferIcon).setVisibility(View.VISIBLE);
.setVisibility(View.VISIBLE);
} else { } else {
view.findViewById(R.id.xferIcon) view.findViewById(R.id.xferIcon).setVisibility(View.INVISIBLE);
.setVisibility(View.INVISIBLE);
} }
return view; return view;

View File

@ -1,18 +1,14 @@
package com.dougkeen.bart.model; package com.dougkeen.bart.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log; import android.util.Log;
@ -23,6 +19,12 @@ import com.dougkeen.bart.activities.ViewDeparturesActivity;
import com.dougkeen.bart.services.BoardedDepartureService; import com.dougkeen.bart.services.BoardedDepartureService;
import com.dougkeen.util.Observable; import com.dougkeen.util.Observable;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Departure implements Parcelable, Comparable<Departure> { public class Departure implements Parcelable, Comparable<Departure> {
private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 5000; private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 5000;
private static final int EXPIRE_MINUTES_AFTER_ARRIVAL = 1; private static final int EXPIRE_MINUTES_AFTER_ARRIVAL = 1;
@ -133,8 +135,9 @@ public class Departure implements Parcelable, Comparable<Departure> {
this.line = line; this.line = line;
} }
public String getTrainDestinationColor() { @ColorInt
return destinationColor; public int getTrainDestinationColor() {
return Color.parseColor(destinationColor);
} }
public void setTrainDestinationColor(String destinationColor) { public void setTrainDestinationColor(String destinationColor) {
@ -627,33 +630,36 @@ public class Departure implements Parcelable, Comparable<Departure> {
final Intent cancelAlarmIntent = new Intent(context, final Intent cancelAlarmIntent = new Intent(context,
BoardedDepartureService.class); BoardedDepartureService.class);
cancelAlarmIntent.putExtra("cancelNotifications", true); cancelAlarmIntent.putExtra("cancelNotifications", true);
String title = getOrigin().shortName + " to " + getPassengerDestination().shortName;
Builder notificationBuilder = new NotificationCompat.Builder(context) Builder notificationBuilder = new NotificationCompat.Builder(context)
.setOngoing(true) .setOngoing(true)
.setSmallIcon(R.drawable.ic_stat_notification) .setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle( .setContentTitle(title)
getOrigin().shortName + " to "
+ getPassengerDestination().shortName)
.setContentIntent(getNotificationIntent(context)).setWhen(0); .setContentIntent(getNotificationIntent(context)).setWhen(0);
if (android.os.Build.VERSION.SDK_INT >= 16) { if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH) notificationBuilder
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentText(minutesText + " until departure"); .setContentText(minutesText + " until departure");
if (isAlarmPending()) { if (isAlarmPending()) {
notificationBuilder.addAction( PendingIntent pendingIntent = PendingIntent.getService(
R.drawable.ic_action_cancel_alarm, context, 0, cancelAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
"Cancel alarm", String subText = "Alarm " + getAlarmLeadTimeMinutes() + " minutes before departure";
PendingIntent.getService(context, 0, cancelAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT)).setSubText( notificationBuilder
"Alarm " + getAlarmLeadTimeMinutes() .addAction(R.drawable.ic_action_cancel_alarm, "Cancel alarm", pendingIntent)
+ " minutes before departure"); .setSubText(subText);
} }
} else if (isAlarmPending()) { } else if (isAlarmPending()) {
notificationBuilder.setContentText(minutesText String text = minutesText
+ " to departure (alarm at " + getAlarmLeadTimeMinutes() + " to departure (alarm at " + getAlarmLeadTimeMinutes()
+ " min" + ((getAlarmLeadTimeMinutes() == 1) ? "" : "s") + " min" + ((getAlarmLeadTimeMinutes() == 1) ? "" : "s")
+ ")"); + ")";
notificationBuilder.setContentText(text);
} else { } else {
notificationBuilder notificationBuilder.setContentText(minutesText + " until departure");
.setContentText(minutesText + " until departure");
} }
return notificationBuilder.build(); return notificationBuilder.build();

View File

@ -4,7 +4,6 @@ import java.lang.ref.WeakReference;
import java.util.List; import java.util.List;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@ -15,6 +14,7 @@ import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.support.v4.app.NotificationManagerCompat;
import com.dougkeen.bart.BartRunnerApplication; import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
@ -34,7 +34,7 @@ public class BoardedDepartureService extends Service implements
private boolean mBound = false; private boolean mBound = false;
private EtdService mEtdService; private EtdService mEtdService;
private StationPair mStationPair; private StationPair mStationPair;
private NotificationManager mNotificationManager; private NotificationManagerCompat mNotificationManager;
private AlarmManager mAlarmManager; private AlarmManager mAlarmManager;
private Handler mHandler; private Handler mHandler;
private boolean mHasShutDown = false; private boolean mHasShutDown = false;
@ -91,7 +91,7 @@ public class BoardedDepartureService extends Service implements
bindService(EtdService_.intent(this).get(), mConnection, bindService(EtdService_.intent(this).get(), mConnection,
Context.BIND_AUTO_CREATE); Context.BIND_AUTO_CREATE);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager = NotificationManagerCompat.from(this);
mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mHandler = new Handler(); mHandler = new Handler();
super.onCreate(); super.onCreate();
@ -302,8 +302,7 @@ public class BoardedDepartureService extends Service implements
.getBoardedDeparture(); .getBoardedDeparture();
if (boardedDeparture != null) { if (boardedDeparture != null) {
mNotificationManager.notify(DEPARTURE_NOTIFICATION_ID, mNotificationManager.notify(DEPARTURE_NOTIFICATION_ID,
boardedDeparture boardedDeparture.createNotification(getApplicationContext()));
.createNotification(getApplicationContext()));
} }
} }

View File

@ -0,0 +1,13 @@
package com.dougkeen.util;
public final class Assert {
// Uninstantiable
private Assert() {}
public static <T> T notNull(T obj) {
if (obj == null) {
throw new AssertionError("Expected object to be non-null");
}
return obj;
}
}

View File

@ -1,77 +1,111 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge <com.dougkeen.bart.controls.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bart="http://schemas.android.com/apk/res-auto" xmlns:bart="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent"> android:layout_height="fill_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
>
<ImageView <!-- This layout is used when the height is >= 480 dp, eg usually in portrait mode. -->
<View
android:id="@+id/destinationColorBar" android:id="@+id/destinationColorBar"
android:layout_width="wrap_content" android:layout_width="15dp"
android:layout_height="64dp" android:layout_height="64dp"
android:layout_alignParentLeft="true" tools:background="#F00"
android:layout_centerVertical="true" />
android:src="@drawable/basic_rectangle" />
<!-- Holds the destination, the train info, and the arrival time -->
<LinearLayout <LinearLayout
android:id="@+id/topRow" android:layout_width="0dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_marginLeft="5dp"
android:layout_toRightOf="@id/destinationColorBar"> android:layout_marginRight="5dp"
android:layout_weight="1"
android:orientation="vertical"
>
<TextView <TextView
android:id="@+id/destinationText" android:id="@+id/destinationText"
style="@style/DepartureDestinationText" style="@style/DepartureDestinationText"
android:layout_weight="1"
android:ellipsize="marquee" android:ellipsize="marquee"
android:singleLine="true" /> android:singleLine="true"
tools:text="Richmond"
<ImageView />
android:id="@+id/bikeIcon"
style="@style/BikeIcon" />
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/countdown"
style="@style/DepartureCountdownText"
android:gravity="right"
android:width="90dp"
bart:tickInterval="1" />
</LinearLayout>
<ImageView
android:id="@+id/xferIcon"
style="@style/XferIcon"
android:layout_alignParentRight="true"
android:layout_below="@id/topRow"
android:src="@drawable/xfer" />
<TextView <TextView
android:id="@+id/trainLengthText" android:id="@+id/trainLengthText"
style="@style/DepartureUncertaintyText" style="@style/DepartureUncertaintyText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/topRow" tools:text="9 cars, platform 2"
android:layout_toRightOf="@id/destinationColorBar" /> />
<TextView
android:id="@+id/uncertainty"
style="@style/DepartureUncertaintyText"
android:layout_alignParentRight="true"
android:layout_below="@id/topRow" />
<TextView <TextView
android:id="@+id/estimatedArrival" android:id="@+id/estimatedArrival"
style="@style/DepartureUncertaintyText" style="@style/DepartureUncertaintyText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/trainLengthText" tools:text="Arrives ~11:12 AM (20 mins)"
android:layout_toRightOf="@id/destinationColorBar" /> />
</LinearLayout>
<!-- Holds the bike and xfer icons -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<ImageView
android:id="@+id/bikeIcon"
style="@style/TrainInfoIcon"
tools:src="@drawable/bike"
/>
<ImageView
android:id="@+id/xferIcon"
style="@style/TrainInfoIcon"
android:src="@drawable/xfer"
/>
</LinearLayout>
<!-- Holds the departure countdown, uncertainty, and departure time -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="right"
android:orientation="vertical"
>
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/countdown"
style="@style/DepartureCountdownText"
android:width="90dp"
android:gravity="right"
bart:tickInterval="1"
tools:text="9m, 57s"
/>
<TextView
android:id="@+id/uncertainty"
style="@style/DepartureUncertaintyText"
tools:text="(+8s)"
/>
<TextView <TextView
android:id="@+id/departureTime" android:id="@+id/departureTime"
style="@style/DepartureUncertaintyText" style="@style/DepartureUncertaintyText"
android:layout_alignParentRight="true" tools:text="11:20 AM"
android:layout_below="@id/uncertainty" /> />
</merge> </LinearLayout>
</com.dougkeen.bart.controls.CheckableLinearLayout>

View File

@ -1,64 +1,98 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge <com.dougkeen.bart.controls.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bart="http://schemas.android.com/apk/res-auto" xmlns:bart="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent"> android:layout_height="fill_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
>
<ImageView <!-- This layout is used when the height is less than 480 dp (eg usually in landscape mode) -->
<View
android:id="@+id/destinationColorBar" android:id="@+id/destinationColorBar"
android:layout_width="wrap_content" android:layout_width="15dp"
android:layout_height="45dp" android:layout_height="45dp"
android:layout_alignParentLeft="true" tools:background="#F00"
android:layout_centerVertical="true" />
android:src="@drawable/basic_rectangle" />
<!-- Holds destination and train info/arrival -->
<LinearLayout <LinearLayout
android:id="@+id/topRow" android:layout_width="wrap_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_marginLeft="5dp"
android:layout_toRightOf="@id/destinationColorBar"> android:layout_marginRight="5dp"
android:layout_weight="1"
android:orientation="vertical"
>
<TextView <TextView
android:id="@+id/destinationText" android:id="@+id/destinationText"
style="@style/DepartureDestinationText" style="@style/DepartureDestinationText"
android:layout_weight="1"
android:ellipsize="marquee" android:ellipsize="marquee"
android:singleLine="true" /> android:singleLine="true"
tools:text="Richmond"
<ImageView />
android:id="@+id/bikeIcon"
style="@style/BikeIcon" />
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/countdown"
style="@style/DepartureCountdownText"
android:gravity="right"
android:width="90dp"
bart:tickInterval="1" />
</LinearLayout>
<ImageView
android:id="@+id/xferIcon"
style="@style/XferIcon"
android:layout_alignParentRight="true"
android:layout_below="@id/topRow"
android:src="@drawable/xfer" />
<com.dougkeen.bart.controls.TimedTextSwitcher <com.dougkeen.bart.controls.TimedTextSwitcher
android:id="@+id/trainLengthText" android:id="@+id/trainLengthText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/topRow" bart:tickInterval="1"
android:layout_toRightOf="@id/destinationColorBar" tools:text="9 cars, platform 2"
bart:tickInterval="1" /> />
</LinearLayout>
<!-- Holds bike and xfer icons -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<ImageView
android:id="@+id/bikeIcon"
style="@style/TrainInfoIcon"
tools:src="@drawable/bike"
/>
<ImageView
android:id="@+id/xferIcon"
style="@style/TrainInfoIcon"
android:src="@drawable/xfer"
/>
</LinearLayout>
<!-- Holds departure countdown and uncertainty/departure time-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="right"
android:orientation="vertical"
>
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/countdown"
style="@style/DepartureCountdownText"
android:width="90dp"
android:gravity="right"
bart:tickInterval="1"
tools:text="9m, 57s"
/>
<com.dougkeen.bart.controls.TimedTextSwitcher <com.dougkeen.bart.controls.TimedTextSwitcher
android:id="@+id/uncertainty" android:id="@+id/uncertainty"
style="@style/DepartureUncertaintyText" style="@style/DepartureUncertaintyText"
android:layout_alignParentRight="true" bart:tickInterval="1"
android:layout_below="@id/topRow" tools:text="(+8s)"
bart:tickInterval="1" /> />
</merge> </LinearLayout>
</com.dougkeen.bart.controls.CheckableLinearLayout>

View File

@ -1,82 +1,121 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bart="http://schemas.android.com/apk/res-auto" > xmlns:bart="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
<TextView
android:id="@+id/alarmText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:orientation="vertical"
android:layout_alignParentTop="true" >
android:drawableLeft="@drawable/ic_action_alarm"
android:gravity="center_vertical" <!-- Holds the "Your Train" title and the alarm indicator -->
android:textSize="16dp" <FrameLayout
android:visibility="gone"></TextView> android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView <TextView
android:id="@+id/yourTrainHeader" android:id="@+id/yourTrainHeader"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_gravity="center_horizontal"
android:layout_centerHorizontal="true"
android:text="@string/your_train" android:text="@string/your_train"
android:textAllCaps="true" android:textAllCaps="true"
android:textSize="20dp" android:textSize="20dp"
android:textStyle="bold" /> android:textStyle="bold"
/>
<ImageView <TextView
android:id="@+id/yourTrainDestinationColorBar" android:id="@+id/alarmText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:drawableLeft="@drawable/ic_action_alarm"
android:gravity="center_vertical"
android:textSize="16dp"
android:visibility="gone"
tools:text="5"
/>
</FrameLayout>
<!-- Holds the color bar, destination text, train info, bike icon, and xfer icon -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<View
android:id="@+id/yourTrainDestinationColorBar"
android:layout_width="15dp"
android:layout_height="45dp" android:layout_height="45dp"
android:layout_alignParentLeft="true" android:layout_gravity="center_vertical"
android:layout_below="@id/yourTrainHeader" tools:background="#F00"
android:src="@drawable/basic_rectangle" /> />
<!-- Holds the destination text and train info -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:padding="5dp"
>
<TextView <TextView
android:id="@+id/yourTrainDestinationText" android:id="@+id/yourTrainDestinationText"
style="@style/DepartureDestinationText" style="@style/DepartureDestinationText"
android:layout_below="@id/yourTrainHeader"
android:layout_toRightOf="@id/yourTrainDestinationColorBar"
android:ellipsize="marquee" android:ellipsize="marquee"
android:singleLine="true" /> android:singleLine="true"
tools:text="Richmond"
<ImageView />
android:id="@+id/yourTrainBikeIcon"
style="@style/BikeIcon"
android:layout_below="@id/yourTrainHeader"
android:layout_toRightOf="@id/yourTrainDestinationText" />
<ImageView
android:id="@+id/yourTrainXferIcon"
style="@style/XferIcon"
android:layout_below="@id/yourTrainBikeIcon"
android:layout_toRightOf="@id/yourTrainDestinationText"
android:src="@drawable/xfer" />
<TextView <TextView
android:id="@+id/yourTrainTrainLengthText" android:id="@+id/yourTrainTrainLengthText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/yourTrainDestinationText" tools:text="9 cars, platform 2"
android:layout_toRightOf="@id/yourTrainDestinationColorBar" />
android:paddingLeft="5dp" />
</LinearLayout>
<!-- Holds the bike icon and xfer icon -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<ImageView
android:id="@+id/yourTrainBikeIcon"
style="@style/TrainInfoIcon"
tools:src="@drawable/bike"
/>
<ImageView
android:id="@+id/yourTrainXferIcon"
style="@style/TrainInfoIcon"
android:src="@drawable/xfer"
/>
</LinearLayout>
</LinearLayout>
<com.dougkeen.bart.controls.CountdownTextView <com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/yourTrainDepartureCountdown" android:id="@+id/yourTrainDepartureCountdown"
style="@style/DepartureCountdownText" style="@style/DepartureCountdownText"
android:layout_alignLeft="@id/yourTrainSection" bart:tickInterval="1"
android:layout_alignRight="@id/yourTrainSection" tools:text="Leaves in 15 min, 20s (+8s)"
android:layout_below="@id/yourTrainTrainLengthText" />
bart:tickInterval="1" />
<com.dougkeen.bart.controls.CountdownTextView <com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/yourTrainArrivalCountdown" android:id="@+id/yourTrainArrivalCountdown"
style="@style/DepartureCountdownText" style="@style/DepartureCountdownText"
android:layout_alignLeft="@id/yourTrainSection"
android:layout_alignRight="@id/yourTrainSection"
android:layout_below="@id/yourTrainDepartureCountdown"
android:ellipsize="end" android:ellipsize="end"
bart:tickInterval="5" /> bart:tickInterval="5"
tools:text="Arrives ~11:12 AM (20 mins)"
/>
</merge> </LinearLayout>

View File

@ -92,18 +92,10 @@
<item name="android:gravity">center</item> <item name="android:gravity">center</item>
</style> </style>
<style name="BikeIcon"> <style name="TrainInfoIcon">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">5dp</item> <item name="android:layout_margin">5dp</item>
<item name="android:layout_marginLeft">5dp</item>
</style>
<style name="XferIcon">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginRight">87dp</item>
<item name="android:layout_marginBottom">5dp</item>
</style> </style>
</resources> </resources>