package edu.mit.mitmobile2.alerts; import java.util.ArrayList; import java.util.HashMap; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.text.Html; import android.text.Spanned; import android.util.Log; import edu.mit.mitmobile2.Global; import edu.mit.mitmobile2.R; import edu.mit.mitmobile2.emergency.EmergencyActivity; import edu.mit.mitmobile2.emergency.EmergencyParser; import edu.mit.mitmobile2.objs.EmergencyItem; import edu.mit.mitmobile2.objs.RouteItem; import edu.mit.mitmobile2.objs.RouteItem.Stops; import edu.mit.mitmobile2.shuttles.RoutesParser; import edu.mit.mitmobile2.shuttles.ShuttleModel; import edu.mit.mitmobile2.shuttles.ShuttlesActivity; import edu.mit.mitmobile2.shuttles.StopsParser; public class NotificationService extends Service { private static final String TAG = "NotificationService"; private static final int EMERGENCY_VERSION_NOT_FOUND = -1; static long SHUTTLE_THRESHOLD = 8*60*1000; Context ctx; SharedPreferences pref; Intent i; int icon; Thread thr; /******************************************************************/ Runnable mTask = new Runnable() { @Override public void run() { if (i==null) return; String action = i.getAction(); if (action==null) return; if (action.equals(NotificationsAlarmReceiver.ACTION_ALARM_SHUTTLE)) { icon = R.drawable.alert_shuttles; checkStop(); } } }; /** * @return ****************************************************************/ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "alert: starting notificationService thread"); super.onStart(intent, startId); i = intent; ctx = getApplicationContext(); pref = ctx.getSharedPreferences(Global.PREFS,MODE_PRIVATE); thr = new Thread(null, mTask, "NotificationService_Service"); thr.start(); return START_FLAG_REDELIVERY; } /******************************************************************/ @Override public void onCreate() { Log.d(TAG, "created notification service"); } /******************************************************************/ protected void checkEmergency() { Log.d(TAG, "checking emergency version tag"); Looper.prepare(); try { // Get status... Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); EmergencyItem ei = EmergencyParser.getStatus(); // Compare with last version... int lastVersion = pref.getInt(Global.PREF_KEY_EMERGENCY_VERSION, EMERGENCY_VERSION_NOT_FOUND); int newVersion = -1; try { if (ei.version!=null) newVersion = Integer.valueOf(ei.version); } catch (NumberFormatException ne) { return; } Log.d(TAG, "Emergency version used to be " + String.valueOf(lastVersion)); Log.d(TAG, "Now it is " + String.valueOf(newVersion)); if (newVersion != lastVersion) { SharedPreferences.Editor editor = pref.edit(); editor.putInt(Global.PREF_KEY_EMERGENCY_VERSION,newVersion); editor.commit(); if (lastVersion != EMERGENCY_VERSION_NOT_FOUND && newVersion >= 0) { String title = getString(R.string.emergency_alert_title); Spanned htmlText = Html.fromHtml(ei.text); notifyUser(title, title, htmlText.toString(), EmergencyActivity.class, 0); } } Looper l = Looper.myLooper(); if (l!=null) { l.quit(); } stopSelf(); } }; EmergencyParser.refreshStatus(this, handler); Looper.loop(); } catch (Exception e) { e.printStackTrace(); } } /******************************************************************/ protected void checkStop() { String stop_title = i.getStringExtra(ShuttleModel.KEY_STOP_TITLE); String route_id = i.getStringExtra(ShuttleModel.KEY_ROUTE_ID); String stop_id = i.getStringExtra(ShuttleModel.KEY_STOP_ID); long alarmTime = i.getLongExtra(ShuttleModel.KEY_TIME,-1); Stops match = null; long curTime = System.currentTimeMillis(); int timeToArrival = (int)(alarmTime - curTime); if (timeToArrival > SHUTTLE_THRESHOLD) { long next = alarmTime; // Query for more accurate time... StopsParser sp = new StopsParser(); RoutesParser rp = new RoutesParser(); sp.getJSON(rp.getBaseUrl()+"?command=stopInfo&id="+stop_id,true); @SuppressWarnings("unchecked") ArrayList<Stops> sss = (ArrayList<Stops>) sp.items; if (sss.isEmpty()) { // stick with same alarm time... } else { // find matching route_id for (Stops s : sss) { Log.d("NotificationService","shuttle-alert: trying to match " + s.route_id); if (route_id.equals(s.route_id)) { match = s; break; } } if (match==null) { Log.e("NotificationService","shuttle-alert: checkStop-> no data"); return; } // given route, now find closest time next = match.next*1000; for (int p : match.predictions) { Log.d("NotificationService","shuttle-alert: closest: " + String.valueOf(next)); if (alarmTime<next) { break; } next =+ p; } alarmTime = next; } /////////////////////////////// // Reschedule... Log.d("NotificationService","shuttle-alert: reschedule: " + String.valueOf(alarmTime) + ", curTime=" + String.valueOf(curTime)); rescheduleShuttleAlarm(stop_title,stop_id,route_id,alarmTime); } else { pref = getSharedPreferences(Global.PREFS_SHUTTLES,Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); // Modify saved alerts (in case UI is running) Log.d("NotificationService","shuttle-alert: try to remove stop " + stop_id); HashMap<String,HashMap <String,Long>> alertIdx = ShuttleModel.getAlerts(pref); HashMap <String,Long> routes_times = alertIdx.get(stop_id); if (routes_times!=null) { Log.d("NotificationService","shuttle-alert: removed: " + route_id); routes_times.remove(route_id); ShuttleModel.saveAlerts(pref, alertIdx); } RouteItem routeItem = ShuttleModel.getRoute(route_id); String title; if (routeItem==null) title = stop_title; else title = routeItem.title; String alertText = String.format("%s in %d minutes", stop_title, timeToArrival / (60 * 1000)); // Arrives soon... notify! notifyUser(title, title, alertText, ShuttlesActivity.class,0); } stopSelf(); } /******************************************************************/ protected void rescheduleShuttleAlarm(String title, String stop, String route, long alarmTime) { AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(Service.ALARM_SERVICE); Intent i = new Intent(ctx, NotificationsAlarmReceiver.class); i.putExtra(ShuttleModel.KEY_STOP_TITLE, title); i.putExtra(ShuttleModel.KEY_STOP_ID, stop); i.putExtra(ShuttleModel.KEY_ROUTE_ID, route); i.putExtra(ShuttleModel.KEY_TIME, alarmTime); PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, i, 0); alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime-4*60*1000, pendingIntent); } /******************************************************************/ protected void notifyUser(String statusBarText, String alarmTitle, String alarmText, Class<?> classname, int offset) { NotificationManager mgr = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); /* A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), >>>>> used to CANCEL it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it. */ //ActivityManager am; //am = (ActivityManager) ctx.getSystemService( Context.ACTIVITY_SERVICE ); /* //List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(100); List<ActivityManager.RunningAppProcessInfo> apps = am.getRunningAppProcesses(); boolean running = false; for (ActivityManager.RunningAppProcessInfo p : apps) { if (p.processName.equalsIgnoreCase(Config.release_project_name)) { if (p.importance==ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) running = true; } } */ Intent startIntent = new Intent(ctx, classname); PendingIntent contentIntent = null; //if (!running) contentIntent = PendingIntent.getActivity(ctx, 0, new Intent(ctx, classname), 0); contentIntent = PendingIntent.getActivity(ctx, 0, startIntent, 0); long curTime = System.currentTimeMillis(); Notification notify = new Notification(icon, statusBarText, curTime); notify.setLatestEventInfo(ctx, alarmTitle, alarmText, contentIntent); notify.flags = Notification.FLAG_AUTO_CANCEL; notify.defaults = Notification.DEFAULT_ALL; //notify.sound = (Uri) intent.getParcelableExtra("Ringtone"); //notify.vibrate = (long[]) intent.getExtras().get("vibrationPatern"); // Launch if selected... mgr.notify((int) curTime+offset, notify); // use current time as id for unique notifications } /******************************************************************/ @Override public IBinder onBind(Intent intent) { return null; } }