328 lines
8.7 KiB
Java
328 lines
8.7 KiB
Java
package com.dougkeen.bart.services;
|
|
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.List;
|
|
|
|
import android.app.AlarmManager;
|
|
import android.app.NotificationManager;
|
|
import android.app.Service;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
|
|
import com.dougkeen.bart.BartRunnerApplication;
|
|
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;
|
|
import com.dougkeen.util.Observer;
|
|
|
|
public class BoardedDepartureService extends Service implements
|
|
EtdServiceListener {
|
|
|
|
private static final int DEPARTURE_NOTIFICATION_ID = 123;
|
|
|
|
private volatile Looper mServiceLooper;
|
|
private volatile ServiceHandler mServiceHandler;
|
|
|
|
private boolean mBound = false;
|
|
private EtdService mEtdService;
|
|
private StationPair mStationPair;
|
|
private NotificationManager mNotificationManager;
|
|
private AlarmManager mAlarmManager;
|
|
private Handler mHandler;
|
|
private boolean mHasShutDown = false;
|
|
|
|
public BoardedDepartureService() {
|
|
super();
|
|
}
|
|
|
|
private static final class ServiceHandler extends Handler {
|
|
private final WeakReference<BoardedDepartureService> mServiceRef;
|
|
|
|
public ServiceHandler(Looper looper,
|
|
BoardedDepartureService boardedDepartureService) {
|
|
super(looper);
|
|
mServiceRef = new WeakReference<BoardedDepartureService>(
|
|
boardedDepartureService);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
BoardedDepartureService service = mServiceRef.get();
|
|
if (service != null) {
|
|
service.onHandleIntent((Intent) msg.obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(BoardedDepartureService.this,
|
|
false);
|
|
}
|
|
mBound = true;
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
HandlerThread thread = new HandlerThread(
|
|
"BartRunnerNotificationService");
|
|
thread.start();
|
|
|
|
mServiceLooper = thread.getLooper();
|
|
mServiceHandler = new ServiceHandler(mServiceLooper, this);
|
|
|
|
bindService(EtdService_.intent(this).get(), 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) {
|
|
mHasShutDown = false;
|
|
onStart(intent, startId);
|
|
return START_REDELIVER_INTENT;
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
shutDown(true);
|
|
if (mBound)
|
|
unbindService(mConnection);
|
|
mServiceLooper.quit();
|
|
super.onDestroy();
|
|
}
|
|
|
|
protected void onHandleIntent(Intent intent) {
|
|
if (intent == null)
|
|
return;
|
|
|
|
final BartRunnerApplication application = (BartRunnerApplication) getApplication();
|
|
final Departure boardedDeparture;
|
|
if (intent.hasExtra("departure")) {
|
|
boardedDeparture = intent.getExtras().getParcelable("departure");
|
|
} else {
|
|
boardedDeparture = application.getBoardedDeparture();
|
|
}
|
|
if (boardedDeparture == null) {
|
|
// Nothing to notify about
|
|
if (mNotificationManager != null) {
|
|
mNotificationManager.cancel(DEPARTURE_NOTIFICATION_ID);
|
|
}
|
|
return;
|
|
}
|
|
if (intent.getBooleanExtra("cancelNotifications", false)
|
|
|| intent.getBooleanExtra("clearBoardedDeparture", false)) {
|
|
// We want to cancel the alarm
|
|
boardedDeparture
|
|
.cancelAlarm(getApplicationContext(), mAlarmManager);
|
|
if (intent.getBooleanExtra("clearBoardedDeparture", false)) {
|
|
application.setBoardedDeparture(null);
|
|
shutDown(false);
|
|
} else {
|
|
updateNotification();
|
|
}
|
|
return;
|
|
}
|
|
|
|
StationPair oldStationPair = mStationPair;
|
|
mStationPair = boardedDeparture.getStationPair();
|
|
|
|
if (mEtdService != null && mStationPair != null
|
|
&& !mStationPair.equals(oldStationPair)) {
|
|
mEtdService.unregisterListener(this);
|
|
}
|
|
|
|
if (getStationPair() != null && mEtdService != null) {
|
|
mEtdService.registerListener(this, false);
|
|
}
|
|
|
|
boardedDeparture.getAlarmLeadTimeMinutesObservable().registerObserver(
|
|
new Observer<Integer>() {
|
|
@Override
|
|
public void onUpdate(Integer newValue) {
|
|
updateNotification();
|
|
}
|
|
});
|
|
boardedDeparture.getAlarmPendingObservable().registerObserver(
|
|
new Observer<Boolean>() {
|
|
@Override
|
|
public void onUpdate(Boolean newValue) {
|
|
updateNotification();
|
|
}
|
|
});
|
|
|
|
updateNotification();
|
|
|
|
pollDepartureStatus();
|
|
}
|
|
|
|
private void updateAlarm() {
|
|
Departure boardedDeparture = ((BartRunnerApplication) getApplication())
|
|
.getBoardedDeparture();
|
|
if (boardedDeparture != null) {
|
|
boardedDeparture
|
|
.updateAlarm(getApplicationContext(), mAlarmManager);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onETDChanged(List<Departure> departures) {
|
|
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
|
|
.getBoardedDeparture();
|
|
for (Departure departure : departures) {
|
|
if (departure.equals(boardedDeparture)
|
|
&& (boardedDeparture.getMeanSecondsLeft() != departure
|
|
.getMeanSecondsLeft() || boardedDeparture
|
|
.getUncertaintySeconds() != departure
|
|
.getUncertaintySeconds())) {
|
|
boardedDeparture.mergeEstimate(departure);
|
|
// Also merge back, in case boardedDeparture estimate is better
|
|
departure.mergeEstimate(boardedDeparture);
|
|
|
|
updateAlarm();
|
|
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 == null || boardedDeparture.hasDeparted()) {
|
|
shutDown(false);
|
|
return;
|
|
}
|
|
|
|
if (mEtdService != null) {
|
|
/*
|
|
* Make sure we're still listening for ETD changes (in case weak ref
|
|
* was garbage collected). Not a huge fan of this approach, but I
|
|
* think I'd rather keep the weak references to avoid memory leaks
|
|
* than move to soft references or some other form of stronger
|
|
* reference. Besides, registerListener() should only result in a
|
|
* few constant-time map operations, so there shouldn't be a big
|
|
* performance hit.
|
|
*/
|
|
mEtdService.registerListener(this, false);
|
|
}
|
|
|
|
boardedDeparture.updateAlarm(getApplicationContext(), mAlarmManager);
|
|
|
|
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);
|
|
}
|
|
if (!isBeingDestroyed)
|
|
stopSelf();
|
|
}
|
|
}
|
|
|
|
private void updateNotification() {
|
|
if (mHasShutDown) {
|
|
if (mEtdService != null) {
|
|
mEtdService.unregisterListener(this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
|
|
.getBoardedDeparture();
|
|
if (boardedDeparture != null) {
|
|
mNotificationManager.notify(DEPARTURE_NOTIFICATION_ID,
|
|
boardedDeparture
|
|
.createNotification(getApplicationContext()));
|
|
}
|
|
}
|
|
|
|
private int getPollIntervalMillis() {
|
|
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
|
|
.getBoardedDeparture();
|
|
|
|
if (boardedDeparture.getSecondsUntilAlarm() > 3 * 60) {
|
|
return 15 * 1000;
|
|
} else {
|
|
return 6 * 1000;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
// Doesn't support binding
|
|
return null;
|
|
}
|
|
|
|
}
|