/* * Copyright (C) 2011 - 2013 Niall 'Rivernile' Scott * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors or contributors be held liable for * any damages arising from the use of this software. * * The aforementioned copyright holder(s) hereby grant you a * non-transferrable right to use this software for any purpose (including * commercial applications), and to modify it and redistribute it, subject to * the following conditions: * * 1. This notice may not be removed or altered from any file it appears in. * * 2. Any modifications made to this software, except those defined in * clause 3 of this agreement, must be released under this license, and * the source code of any modifications must be made available on a * publically accessible (and locateable) website, or sent to the * original author of this software. * * 3. Software modifications that do not alter the functionality of the * software but are simply adaptations to a specific environment are * exempt from clause 2. */ package uk.org.rivernile.edinburghbustracker.android.alerts; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.location.LocationManager; import android.os.SystemClock; import com.google.android.gms.maps.model.LatLng; import uk.org.rivernile.edinburghbustracker.android.BusStopDatabase; import uk.org.rivernile.edinburghbustracker.android.SettingsDatabase; /** * The AlertManager takes care of handling the addition and removal of proximity * and time based alerts. * * @author Niall Scott */ public class AlertManager { private static AlertManager instance; private Context context; private LocationManager locMan; private AlarmManager alMan; private BusStopDatabase bsd; private SettingsDatabase sd; /** * Create a new AlertManager instance. * * @param context The Application context. */ private AlertManager(final Context context) { locMan = (LocationManager)context.getSystemService( Context.LOCATION_SERVICE); alMan = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); bsd = BusStopDatabase.getInstance(context.getApplicationContext()); sd = SettingsDatabase.getInstance(context.getApplicationContext()); } /** * Get an instance of this class. It is a singleton. * * @param context The Application context. * @return An AlertManager instance. */ public static AlertManager getInstance(final Context context) { if(context == null) throw new IllegalArgumentException("The context should not be " + "null."); if(instance == null) instance = new AlertManager(context); instance.context = context; return instance; } /** * Add a new proximity alert. The criteria is the bus stop code and the * maximum distance from that particular bus stop. * * @param stopCode The bus stop to alert when in proximity of. * @param distance The maximum distance from the bus stop. * @see #removeProximityAlert() */ public void addProximityAlert(final String stopCode, final int distance) { if(stopCode == null || stopCode.length() == 0) throw new IllegalArgumentException("The stopCode cannot be null " + "or blank."); // Remove any other existing proximity alerts. removeProximityAlert(); // Get the coordinates of the bus stop. final LatLng point = bsd.getLatLngForStopCode(stopCode); double latitude = 0; double longitude = 0; if(point != null) { latitude = point.latitude; longitude = point.longitude; } // The intent to send to the BroadcastReceiver when the distance // criteria has been met. final Intent intent = new Intent(context, ProximityAlertReceiver.class); intent.putExtra(ProximityAlertReceiver.ARG_STOPCODE, stopCode); intent.putExtra(ProximityAlertReceiver.ARG_DISTANCE, distance); final PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); // Make sure the LocationManager is not looking out for any other // locations for the alerts. locMan.removeProximityAlert(pi); // Add the new alert to the database. sd.insertNewProximityAlert(stopCode, distance); // Ask LocationManager to look out for the given location. locMan.addProximityAlert(latitude, longitude, (float)distance, System.currentTimeMillis() + 3600000, pi); } /** * Remove any current proximity alerts. Only 1 can be set at a time, hence * why this method does not need any sort of id argument. * * @see #addProximityAlert(java.lang.String, int) */ public void removeProximityAlert() { // Remove the alert from the database. sd.deleteAllAlertsOfType(SettingsDatabase.ALERTS_TYPE_PROXIMITY); final Intent intent = new Intent(context, ProximityAlertReceiver.class); final PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); // Remove the proximity alert from LocationManager. locMan.removeProximityAlert(pi); // Make sure the PendingIntent does not remain active. pi.cancel(); } /** * Add a new time alert. Only a single stopCode can be monitored, but any * number of services can be monitored. A time trigger is specified which * denotes the maximum time the bus is from the stop when the alert is * triggered. * * @param stopCode The bus stop code to monitor. * @param services A String array of bus service names to monitor for. * @param timeTrigger The maximum amount of time the bus should be from the * bus stop before an alert is triggered. * @see #removeTimeAlert() */ public void addTimeAlert(final String stopCode, final String[] services, final int timeTrigger) { if(stopCode == null || stopCode.length() == 0) throw new IllegalArgumentException("The stopCode cannot be null " + "or blank."); if(services == null || services.length == 0) throw new IllegalArgumentException("The services list cannot be " + "null or empty."); if(timeTrigger < 0) throw new IllegalArgumentException("The timeTrigger cannot be " + "less than 0."); // Make sure any other time alerts do not exist. removeTimeAlert(); // The intent to send to the service which monitors the bus times. final Intent intent = new Intent(context, TimeAlertService.class); intent.putExtra(TimeAlertService.ARG_STOPCODE, stopCode); intent.putExtra(TimeAlertService.ARG_SERVICES, services); intent.putExtra(TimeAlertService.ARG_TIME_TRIGGER, timeTrigger); intent.putExtra(TimeAlertService.ARG_TIME_SET, SystemClock.elapsedRealtime()); final PendingIntent pi = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); // Make sure existing alarms are cancelled. alMan.cancel(pi); // Add a new time alert to the database. sd.insertNewTimeAlert(stopCode, services, timeTrigger); // Set the alarm. alMan.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60000, pi); } /** * Remove any current time alerts. Only 1 can be set at a time, hence why * this method does not need any sort of id argument. * * @see #addTimeAlert(java.lang.String, java.lang.String[], int) */ public void removeTimeAlert() { // Remove all time alerts from the database. sd.deleteAllAlertsOfType(SettingsDatabase.ALERTS_TYPE_TIME); final Intent intent = new Intent(context, TimeAlertService.class); final PendingIntent pi = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); // Cancel any pending checks with the AlarmManager. alMan.cancel(pi); // Make sure the PendingIntent is cancelled and invalid too. pi.cancel(); } }