BartRunnerAndroid/src/com/dougkeen/bart/services/NotificationService.java

381 lines
11 KiB
Java
Raw Normal View History

package com.dougkeen.bart.services;
2012-09-17 15:13:50 +00:00
import java.util.List;
2012-09-19 21:12:18 +00:00
import org.apache.commons.lang3.time.DateFormatUtils;
2012-09-17 15:13:50 +00:00
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
2012-09-17 15:13:50 +00:00
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
2012-09-17 15:13:50 +00:00
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
2012-09-17 15:13:50 +00:00
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
2012-09-19 21:12:18 +00:00
import android.util.Log;
2012-09-17 15:13:50 +00:00
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
2012-09-17 15:13:50 +00:00
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener;
2012-09-17 15:13:50 +00:00
public class NotificationService extends Service implements EtdServiceListener {
2012-09-17 15:13:50 +00:00
private static final int DEPARTURE_NOTIFICATION_ID = 123;
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
2012-09-17 15:13:50 +00:00
private boolean mBound = false;
private EtdService mEtdService;
private StationPair mStationPair;
private NotificationManager mNotificationManager;
private AlarmManager mAlarmManager;
private PendingIntent mNotificationIntent;
private PendingIntent mAlarmPendingIntent;
private int mAlertLeadTime;
private Handler mHandler;
private boolean mHasShutDown = false;
2012-09-17 15:13:50 +00:00
public NotificationService() {
super();
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent) msg.obj);
}
2012-09-17 15:13:50 +00:00
}
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
mEtdService = null;
mBound = false;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mEtdService = ((EtdServiceBinder) service).getService();
if (getStationPair() != null) {
mEtdService.registerListener(NotificationService.this, false);
2012-09-17 15:13:50 +00:00
}
mBound = true;
}
};
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread(
"BartRunnerNotificationService");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
2012-09-17 15:13:50 +00:00
bindService(new Intent(this, EtdService.class), mConnection,
Context.BIND_AUTO_CREATE);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mHandler = new Handler();
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
2012-09-19 21:12:18 +00:00
mHasShutDown = false;
onStart(intent, startId);
return START_STICKY;
}
2012-09-17 15:13:50 +00:00
@Override
public void onDestroy() {
shutDown(true);
2012-09-17 15:13:50 +00:00
if (mBound)
unbindService(mConnection);
mServiceLooper.quit();
2012-09-17 15:13:50 +00:00
super.onDestroy();
}
protected void onHandleIntent(Intent intent) {
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
2012-09-19 21:12:18 +00:00
if (boardedDeparture == null
|| intent.getBooleanExtra("cancelAlarm", false)) {
// Nothing to notify about, or we want to cancel the alarm
shutDown(false);
return;
}
2012-09-17 15:13:50 +00:00
Bundle bundle = intent.getExtras();
StationPair oldStationPair = mStationPair;
mStationPair = boardedDeparture.getStationPair();
2012-09-17 15:13:50 +00:00
mAlertLeadTime = bundle.getInt("alertLeadTime");
if (mEtdService != null && mStationPair != null
&& !mStationPair.equals(oldStationPair)) {
2012-09-17 15:13:50 +00:00
mEtdService.unregisterListener(this);
}
if (getStationPair() != null && mEtdService != null) {
mEtdService.registerListener(this, false);
2012-09-17 15:13:50 +00:00
}
Intent targetIntent = new Intent(Intent.ACTION_VIEW,
mStationPair.getUri());
targetIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mNotificationIntent = PendingIntent.getActivity(this, 0, targetIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
2012-09-19 21:12:18 +00:00
updateNotification();
2012-09-17 15:13:50 +00:00
setAlarm();
pollDepartureStatus();
}
private void refreshAlarmPendingIntent() {
final Intent alarmIntent = new Intent(Constants.ACTION_ALARM,
getStationPair().getUri());
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
alarmIntent.putExtra("departure", (Parcelable) boardedDeparture);
2012-09-17 15:13:50 +00:00
mAlarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
private void setAlarm() {
cancelAlarm();
if (mAlertLeadTime > 0) {
long alertTime = getAlarmClockTime();
2012-09-17 15:13:50 +00:00
if (alertTime > System.currentTimeMillis()) {
2012-09-19 21:12:18 +00:00
if (Log.isLoggable(Constants.TAG, Log.VERBOSE))
Log.v(Constants.TAG, "Scheduling alarm for "
+ DateFormatUtils.format(alertTime, "h:mm:ss"));
2012-09-17 15:13:50 +00:00
refreshAlarmPendingIntent();
mAlarmManager.set(AlarmManager.RTC_WAKEUP, alertTime,
mAlarmPendingIntent);
2012-09-19 21:12:18 +00:00
((BartRunnerApplication) getApplication())
.setAlarmPending(true);
2012-09-17 15:13:50 +00:00
}
}
}
private void triggerAlarmImmediately() {
2012-09-19 21:12:18 +00:00
Log.v(Constants.TAG, "Setting off alarm immediately");
2012-09-17 15:13:50 +00:00
cancelAlarm();
refreshAlarmPendingIntent();
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 100, mAlarmPendingIntent);
}
private long getAlarmClockTime() {
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
return boardedDeparture.getMeanEstimate() - mAlertLeadTime * 60 * 1000;
2012-09-17 15:13:50 +00:00
}
private void cancelAlarm() {
2012-09-19 21:12:18 +00:00
((BartRunnerApplication) getApplication()).setAlarmPending(false);
if (mAlarmManager != null) {
mAlarmManager.cancel(mAlarmPendingIntent);
}
2012-09-17 15:13:50 +00:00
}
@Override
public void onETDChanged(List<Departure> departures) {
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
2012-09-17 15:13:50 +00:00
for (Departure departure : departures) {
if (departure.equals(boardedDeparture)
&& (boardedDeparture.getMeanSecondsLeft() != departure
.getMeanSecondsLeft() || boardedDeparture
2012-09-17 15:13:50 +00:00
.getUncertaintySeconds() != departure
.getUncertaintySeconds())) {
long initialAlertClockTime = getAlarmClockTime();
2012-09-17 15:13:50 +00:00
boardedDeparture.mergeEstimate(departure);
2012-09-17 15:13:50 +00:00
final long now = System.currentTimeMillis();
final long newAlarmClockTime = getAlarmClockTime();
if (initialAlertClockTime > now && newAlarmClockTime <= now) {
2012-09-17 15:13:50 +00:00
// Alert time was changed to the past
triggerAlarmImmediately();
} else if (newAlarmClockTime > now) {
2012-09-17 15:13:50 +00:00
// Alert time is still in the future
setAlarm();
}
break;
}
}
}
@Override
public void onError(String errorMessage) {
// Do nothing
}
@Override
public void onRequestStarted() {
// Do nothing
}
@Override
public void onRequestEnded() {
// Do nothing
}
@Override
public StationPair getStationPair() {
return mStationPair;
}
private long mNextScheduledCheckClockTime = 0;
private void pollDepartureStatus() {
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
if (boardedDeparture.hasDeparted()) {
shutDown(false);
2012-09-17 15:13:50 +00:00
}
// Departure must have changed... fire the alarm
if (getAlarmClockTime() < System.currentTimeMillis()) {
triggerAlarmImmediately();
}
2012-09-17 15:13:50 +00:00
updateNotification();
final int pollIntervalMillis = getPollIntervalMillis();
final long scheduledCheckClockTime = System.currentTimeMillis()
+ pollIntervalMillis;
if (mNextScheduledCheckClockTime < scheduledCheckClockTime) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
pollDepartureStatus();
}
}, pollIntervalMillis);
mNextScheduledCheckClockTime = scheduledCheckClockTime;
}
}
private void shutDown(boolean isBeingDestroyed) {
if (!mHasShutDown) {
mHasShutDown = true;
if (mEtdService != null) {
mEtdService.unregisterListener(this);
}
if (mNotificationManager != null) {
mNotificationManager.cancel(DEPARTURE_NOTIFICATION_ID);
}
cancelAlarm();
if (!isBeingDestroyed)
stopSelf();
}
2012-09-17 15:13:50 +00:00
}
private void updateNotification() {
if (mHasShutDown) {
2012-09-19 21:12:18 +00:00
if (mEtdService != null) {
mEtdService.unregisterListener(this);
}
return;
}
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
final int halfMinutes = (boardedDeparture.getMeanSecondsLeft() + 15) / 30;
float minutes = halfMinutes / 2f;
final String minutesText = (minutes < 1) ? "Less than one minute"
: (String.format("~%.1f minute", minutes) + ((minutes != 1.0) ? "s"
: ""));
2012-09-19 21:12:18 +00:00
final Intent cancelAlarmIntent = new Intent(getApplicationContext(),
NotificationService.class);
cancelAlarmIntent.putExtra("cancelAlarm", true);
Builder notificationBuilder = new NotificationCompat.Builder(this)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(
mStationPair.getOrigin().shortName + " to "
+ mStationPair.getDestination().shortName)
.setContentIntent(mNotificationIntent).setWhen(0);
if (android.os.Build.VERSION.SDK_INT >= 16) {
2012-09-19 21:12:18 +00:00
notificationBuilder
.setPriority(NotificationCompat.PRIORITY_HIGH)
.addAction(
R.drawable.ic_action_cancel_alarm,
"Cancel alarm",
PendingIntent.getService(getApplicationContext(),
0, cancelAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.setContentText(minutesText + " until departure")
.setSubText(
"Alert " + mAlertLeadTime
+ " minutes before departure");
} else {
notificationBuilder.setContentText(minutesText
+ " to departure (alarm at " + mAlertLeadTime + " min"
+ ((mAlertLeadTime == 1) ? "" : "s") + ")");
}
2012-09-17 15:13:50 +00:00
mNotificationManager.notify(DEPARTURE_NOTIFICATION_ID,
notificationBuilder.build());
2012-09-17 15:13:50 +00:00
}
private int getPollIntervalMillis() {
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
final int secondsToAlarm = boardedDeparture.getMeanSecondsLeft()
2012-09-17 15:13:50 +00:00
- mAlertLeadTime * 60;
if (secondsToAlarm < -20) {
/* Alarm should have already gone off by now */
shutDown(false);
2012-09-17 15:13:50 +00:00
return 10000000; // Arbitrarily large number
}
2012-09-19 21:12:18 +00:00
if (secondsToAlarm > 3 * 60) {
return 15 * 1000;
2012-09-17 15:13:50 +00:00
} else {
return 6 * 1000;
2012-09-17 15:13:50 +00:00
}
}
@Override
public IBinder onBind(Intent intent) {
// Doesn't support binding
return null;
}
2012-09-17 15:13:50 +00:00
}