/* * Copyright (C) 2016 Paul Watts (paulcwatts@gmail.com), * University of South Florida (sjbarbeau@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.android.tripservice; import org.onebusaway.android.provider.ObaContract; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.text.format.Time; /** * This is the runnable that implements scheduling of trips (for the reminder feature). * It can schedule one or many trips, depending on the URI. * * @author paulw */ public final class SchedulerTask implements Runnable { //private static final String TAG = "SchedulerTask"; private static final long ONE_MINUTE = 60 * 1000; private static final long LOOKAHEAD_DURATION_MS = 5 * ONE_MINUTE; private static final String[] PROJECTION = { ObaContract.Trips._ID, ObaContract.Trips.STOP_ID, ObaContract.Trips.REMINDER, ObaContract.Trips.DEPARTURE, ObaContract.Trips.DAYS }; private static final int COL_ID = 0; private static final int COL_STOP_ID = 1; private static final int COL_REMINDER = 2; private static final int COL_DEPARTURE = 3; private static final int COL_DAYS = 4; private final Context mContext; private final ContentResolver mCR; private final TaskContext mTaskContext; private final Uri mUri; public SchedulerTask(Context context, TaskContext taskContext, Uri uri) { mContext = context; mCR = mContext.getContentResolver(); mTaskContext = taskContext; mUri = uri; } @Override public void run() { cleanupOldAlerts(); Cursor c = mCR.query(mUri, PROJECTION, null, null, null); try { Time tNow = new Time(); tNow.setToNow(); final long now = tNow.toMillis(false); if (c != null) { while (c.moveToNext()) { schedule1(c, tNow, now); } } } finally { if (c != null) { c.close(); } mTaskContext.taskComplete(); } } // This schedules an alarm to go off when we need it to start polling, // and instantiates an TripAlert in the database if needed. private void schedule1(Cursor c, Time tNow, long now) { final String tripId = c.getString(COL_ID); final String stopId = c.getString(COL_STOP_ID); final Uri tripUri = ObaContract.Trips.buildUri(tripId, stopId); final int departureMins = c.getInt(COL_DEPARTURE); final long reminderMS = c.getInt(COL_REMINDER) * ONE_MINUTE; if (reminderMS == 0) { return; } final int days = c.getInt(COL_DAYS); if (days == 0) { Time tmp = new Time(); tmp.set(0, departureMins, 0, tNow.monthDay, tNow.month, tNow.year); tmp.normalize(false); long remindTime = tmp.toMillis(false) - reminderMS; long triggerTime = remindTime - LOOKAHEAD_DURATION_MS; if (!scheduleAlert(tripUri, tripId, stopId, triggerTime)) { // If we failed to schedule a one-off alert, then it's // probably been cancelled or in the past and we should // just delete it. mCR.delete(tripUri, null, null); } } else { final int currentWeekDay = tNow.weekDay; for (int i = 0; i < 7; ++i) { final int day = (currentWeekDay + i) % 7; final int bit = ObaContract.Trips.getDayBit(day); if ((days & bit) == bit) { Time tmp = new Time(); tmp.set(0, departureMins, 0, tNow.monthDay + i, tNow.month, tNow.year); tmp.normalize(false); long remindTime = tmp.toMillis(false) - reminderMS; long triggerTime = remindTime - LOOKAHEAD_DURATION_MS; if (scheduleAlert(tripUri, tripId, stopId, triggerTime)) { return; } } } } } private boolean scheduleAlert(Uri uri, String tripId, String stopId, long triggerTime) { Time tmp = new Time(); tmp.set(triggerTime); //Log.d(TAG, "Scheduling poll: " + uri.toString() + " " // + tmp.format2445()); // Check to see if this alert has already been cancelled. Uri alertUri = null; Cursor cAlert = mCR.query(ObaContract.TripAlerts.CONTENT_URI, new String[]{ObaContract.TripAlerts._ID, ObaContract.TripAlerts.STATE}, String.format("%s=? AND %s=? AND %s=?", ObaContract.TripAlerts.TRIP_ID, ObaContract.TripAlerts.STOP_ID, ObaContract.TripAlerts.START_TIME), new String[]{tripId, stopId, String.valueOf(triggerTime)}, null); if (cAlert != null) { try { if (cAlert.moveToNext()) { if (cAlert.getInt(1) == ObaContract.TripAlerts.STATE_CANCELLED) { return false; } alertUri = ObaContract.TripAlerts.buildUri(cAlert.getInt(0)); } } finally { cAlert.close(); } } if (alertUri == null) { // Insert a new trip alert. ContentValues values = new ContentValues(); values.put(ObaContract.TripAlerts.TRIP_ID, tripId); values.put(ObaContract.TripAlerts.STOP_ID, stopId); values.put(ObaContract.TripAlerts.START_TIME, triggerTime); alertUri = mCR.insert(ObaContract.TripAlerts.CONTENT_URI, values); } // Should we schedule it in every case here??? What about when it's // already polling??? TripService.pollTrip(mContext, alertUri, triggerTime); return true; } /** * Remove any alerts that are more than 24 hours in the past. */ private void cleanupOldAlerts() { long then = System.currentTimeMillis() - ONE_MINUTE * 60 * 24; mCR.delete(ObaContract.TripAlerts.CONTENT_URI, ObaContract.TripAlerts.START_TIME + " < " + then, null); } }