Java tutorial
/* * Copyright (c) 2011-2013 by Curt Binder (http://curtbinder.info) * * This work is made available under the terms of the Creative Commons * Attribution-NonCommercial-ShareAlike 3.0 Unported License * http://creativecommons.org/licenses/by-nc-sa/3.0/ */ package info.curtbinder.reefangel.service; import info.curtbinder.reefangel.controller.Controller; import info.curtbinder.reefangel.db.ErrorTable; import info.curtbinder.reefangel.db.NotificationTable; import info.curtbinder.reefangel.db.StatusProvider; import info.curtbinder.reefangel.db.StatusTable; import info.curtbinder.reefangel.phone.Globals; import info.curtbinder.reefangel.phone.Permissions; import info.curtbinder.reefangel.phone.R; import info.curtbinder.reefangel.phone.RAApplication; import info.curtbinder.reefangel.phone.StatusActivity; import java.util.Locale; import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.support.v4.app.NotificationCompat; public class NotificationService extends IntentService { private static final String TAG = NotificationService.class.getSimpleName(); private static RAApplication rapp; private String errorMessage; private String paramPrecision; private String[] parameters; public NotificationService() { super(TAG); } @Override protected void onHandleIntent(Intent intent) { rapp = (RAApplication) getApplication(); String action = intent.getAction(); if (action.equals(MessageCommands.NOTIFICATION_CLEAR_INTENT)) { clearNotifications(); } else if (action.equals(MessageCommands.NOTIFICATION_LAUNCH_INTENT)) { clearNotifications(); Intent i = getStatusActivity(); getApplication().startActivity(i); } else if (action.equals(MessageCommands.NOTIFICATION_INTENT)) { processNotifications(); } else if (action.equals(MessageCommands.NOTIFICATION_ERROR_INTENT)) { processError(); } } private void processError() { boolean fDisplayUpdate = true; if (rapp.raprefs.isNotificationEnabled()) { // Only proceed if notifications are enabled // check if can retry on errors if (rapp.raprefs.isErrorRetryEnabled()) { // increase error count as soon as we know it's an error rapp.increaseErrorCount(); // we are to retry connection before displaying an error if (rapp.canErrorRetry()) { // if error count is less than the max, // we need to retry the communication String s = rapp.getString(R.string.messageErrorRetry, rapp.errorCount); broadcastUpdateStatus(s); fDisplayUpdate = false; try { Thread.sleep(rapp.raprefs.getNotificationErrorRetryInterval()); } catch (InterruptedException e) { } Intent i = new Intent(rapp, UpdateService.class); i.setAction(MessageCommands.QUERY_STATUS_INTENT); startService(i); } // otherwise if we have exceeded the max count, then we // display the error } } if (fDisplayUpdate) { // log the error in the error table String errorMessage = rapp.getErrorMessage(); insertErrorMessage(errorMessage); // notify the user notifyUser(); // update the app text broadcastUpdateStatus(errorMessage); } } private void broadcastUpdateStatus(String status) { Intent i = new Intent(MessageCommands.UPDATE_STATUS_INTENT); i.putExtra(MessageCommands.UPDATE_STATUS_ID, -1); i.putExtra(MessageCommands.UPDATE_STATUS_STRING, status); rapp.sendBroadcast(i, Permissions.QUERY_STATUS); } private void processNotifications() { Uri uri = Uri.parse(StatusProvider.CONTENT_URI + "/" + StatusProvider.PATH_NOTIFICATION); // get all notifications Cursor c = getContentResolver().query(uri, null, null, null, NotificationTable.COL_ID + " ASC"); int notifyCount = 0; if (c.moveToFirst()) { int param = 0, cond = 0; float value = (float) 0.0; parameters = rapp.getResources().getStringArray(R.array.deviceParameters); // grab the latest parameters to compare against Uri uriLatest = Uri.parse(StatusProvider.CONTENT_URI + "/" + StatusProvider.PATH_LATEST); // get all notifications Cursor l = getContentResolver().query(uriLatest, null, null, null, StatusTable.COL_ID + " DESC"); l.moveToFirst(); do { param = c.getInt(c.getColumnIndex(NotificationTable.COL_PARAM)); cond = c.getInt(c.getColumnIndex(NotificationTable.COL_CONDITION)); value = c.getFloat(c.getColumnIndex(NotificationTable.COL_VALUE)); errorMessage = ""; if (isNotifyTriggered(param, cond, value, l)) { // notification triggered // add to list of notifications insertErrorMessage(errorMessage); notifyCount++; } } while (c.moveToNext()); l.close(); } // else no notifications c.close(); // launch the notification after we process the messages if (notifyCount > 0) { notifyUser(); } } private boolean isNotifyTriggered(int param, int cond, float rvalue, Cursor latest) { boolean fRet = false; float lvalue = getLeftValue(param, latest); String condLabel = ""; switch (cond) { case Globals.condEqual: { if (lvalue == rvalue) { condLabel = "="; fRet = true; } break; } case Globals.condGreaterThan: { if (lvalue > rvalue) { condLabel = ">"; fRet = true; } break; } case Globals.condGreaterThanOrEqualTo: { if (lvalue >= rvalue) { condLabel = ">="; fRet = true; } break; } case Globals.condLessThan: { if (lvalue < rvalue) { condLabel = "<"; fRet = true; } break; } case Globals.condLessThanOrEqualTo: { if (lvalue <= rvalue) { condLabel = "<="; fRet = true; } break; } case Globals.condNotEqual: { if (lvalue != rvalue) { condLabel = "!="; fRet = true; } break; } } if (fRet) { String formatString = String.format(Locale.US, "%%s: %s %s %s", paramPrecision, condLabel, paramPrecision); errorMessage = String.format(Locale.US, formatString, parameters[param], lvalue, rvalue); } return fRet; } private float getLeftValue(int id, Cursor l) { float f; paramPrecision = "%.0f"; switch (id) { case Globals.paramT1: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_T1)); paramPrecision = "%.1f"; break; } case Globals.paramT2: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_T2)); paramPrecision = "%.1f"; break; } case Globals.paramT3: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_T3)); paramPrecision = "%.1f"; break; } case Globals.paramPH: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PH)); paramPrecision = "%.2f"; break; } case Globals.paramPHExpansion: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PHE)); paramPrecision = "%.2f"; break; } case Globals.paramDaylightPWM: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_DP)); break; } case Globals.paramActinicPWM: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_AP)); break; } case Globals.paramSalinity: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_SAL)); paramPrecision = "%.1f"; break; } case Globals.paramORP: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_ORP)); break; } case Globals.paramWaterLevel: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_WL)); break; } case Globals.paramATOHigh: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_ATOHI)); break; } case Globals.paramATOLow: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_ATOLO)); break; } case Globals.paramPWMExp0: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PWME0)); break; } case Globals.paramPWMExp1: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PWME1)); break; } case Globals.paramPWMExp2: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PWME2)); break; } case Globals.paramPWMExp3: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PWME3)); break; } case Globals.paramPWMExp4: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PWME4)); break; } case Globals.paramPWMExp5: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_PWME5)); break; } case Globals.paramAIWhite: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_AIW)); break; } case Globals.paramAIBlue: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_AIB)); break; } case Globals.paramAIRoyalBlue: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_AIRB)); break; } case Globals.paramVortechMode: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFM)); break; } case Globals.paramVortechSpeed: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFS)); break; } case Globals.paramVortechDuration: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFD)); break; } case Globals.paramRadionWhite: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFW)); break; } case Globals.paramRadionRoyalBlue: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFRB)); break; } case Globals.paramRadionRed: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFR)); break; } case Globals.paramRadionGreen: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFG)); break; } case Globals.paramRadionBlue: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFB)); break; } case Globals.paramRadionIntensity: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_RFI)); break; } case Globals.paramIOCh0: case Globals.paramIOCh1: case Globals.paramIOCh2: case Globals.paramIOCh3: case Globals.paramIOCh4: case Globals.paramIOCh5: { short io = l.getShort(l.getColumnIndex(StatusTable.COL_IO)); byte ch = (byte) (id - Globals.paramIOCh0); // getIOChannel returns TRUE if the value is 1 // and FALSE if the value is 0 if (Controller.getIOChannel(io, ch)) { f = 1; } else { f = 0; } break; } case Globals.paramCustom0: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C0)); break; } case Globals.paramCustom1: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C1)); break; } case Globals.paramCustom2: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C2)); break; } case Globals.paramCustom3: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C3)); break; } case Globals.paramCustom4: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C4)); break; } case Globals.paramCustom5: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C5)); break; } case Globals.paramCustom6: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C6)); break; } case Globals.paramCustom7: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_C7)); break; } case Globals.paramWaterLevel1: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_WL1)); break; } case Globals.paramWaterLevel2: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_WL2)); break; } case Globals.paramWaterLevel3: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_WL3)); break; } case Globals.paramWaterLevel4: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_WL4)); break; } case Globals.paramHumidity: { f = l.getFloat(l.getColumnIndex(StatusTable.COL_HUM)); break; } default: { f = 0; break; } } return f; } private PendingIntent getNotificationLaunchIntent(boolean fClearOnly) { // Create notification intent // Will launch the service to clear the notifications // and launch the main activity unless clear only is set Intent i = new Intent(this, NotificationService.class); if (fClearOnly) { i.setAction(MessageCommands.NOTIFICATION_CLEAR_INTENT); } else { i.setAction(MessageCommands.NOTIFICATION_LAUNCH_INTENT); } PendingIntent pi = PendingIntent.getService(this, -1, i, 0); return pi; } private NotificationCompat.Builder buildNormalNotification(String msg, long when, int count) { Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_icon); NotificationCompat.Builder b = new NotificationCompat.Builder(this).setAutoCancel(true) .setSmallIcon(R.drawable.st_notify).setLargeIcon(icon).setContentTitle(getString(R.string.app_name)) .setContentText(msg).setTicker(msg).setWhen(when).setSound(rapp.raprefs.getNotificationSound()) .setDeleteIntent(getNotificationLaunchIntent(true)) .setContentIntent(getNotificationLaunchIntent(false)); if (count > 1) { b.setNumber(count); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { String msgGB = String.format(Locale.US, getString(R.string.messageGBMoreErrorss), msg, count); b.setContentText(msgGB); } } return b; } private String getInboxStyleMessage(String msg, long when) { String extraMessage = String.format(Locale.getDefault(), "%s - %s", msg, RAApplication.getFancyDate(when)); return extraMessage; } public void notifyUser() { Uri uri = Uri.parse(StatusProvider.CONTENT_URI + "/" + StatusProvider.PATH_ERROR); Cursor c = getContentResolver().query(uri, null, ErrorTable.COL_READ + "=?", new String[] { "0" }, ErrorTable.COL_ID + " DESC"); String firstMessage = null; long firstWhen = 0; int numCount = 0; String[] summaryLines = new String[5]; String summaryText = ""; if (c.moveToFirst()) { int msgIndex = c.getColumnIndex(ErrorTable.COL_MESSAGE); int whenIndex = c.getColumnIndex(ErrorTable.COL_TIME); // grab the most recent error first firstMessage = c.getString(msgIndex); firstWhen = c.getLong(whenIndex); numCount = c.getCount(); // handle looping through the rest of the messages // in order to create the big notification // InboxStyle only allows for up to 5 lines int extraCount = 1; summaryLines[0] = getInboxStyleMessage(firstMessage, firstWhen); while (c.moveToNext() && extraCount < 5) { summaryLines[extraCount] = getInboxStyleMessage(c.getString(msgIndex), c.getLong(whenIndex)); extraCount++; } // when multiple items shown, the first item is the content title // so a max of items can be shown (content title plus 5 extra // lines) if (extraCount < numCount) { summaryText = String.format(Locale.US, getString(R.string.messageMoreErrors), numCount - extraCount); } } c.close(); NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); int mNotificationId = 001; NotificationCompat.Builder normal = buildNormalNotification(firstMessage, firstWhen, numCount); if (numCount > 1) { NotificationCompat.InboxStyle inbox = new NotificationCompat.InboxStyle(normal); // inbox.setBigContentTitle( firstMessage ); for (String s : summaryLines) { inbox.addLine(s); } inbox.setSummaryText(summaryText); nm.notify(mNotificationId, inbox.build()); } else { nm.notify(mNotificationId, normal.build()); } } public void insertErrorMessage(String msg) { // inserts the given error message into the database // message is the parameter for expandability with notifications ContentValues v = new ContentValues(); v.put(ErrorTable.COL_TIME, System.currentTimeMillis()); v.put(ErrorTable.COL_MESSAGE, msg); v.put(ErrorTable.COL_READ, false); getContentResolver().insert(Uri.parse(StatusProvider.CONTENT_URI + "/" + StatusProvider.PATH_ERROR), v); } private Intent getStatusActivity() { Intent si = new Intent(this, StatusActivity.class); si.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); si.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); si.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); return si; } private void clearNotifications() { Uri uri = Uri.parse(StatusProvider.CONTENT_URI + "/" + StatusProvider.PATH_ERROR); ContentValues v = new ContentValues(); v.put(ErrorTable.COL_READ, true); // ignore the number of rows updated getContentResolver().update(uri, v, ErrorTable.COL_READ + "=?", new String[] { "0" }); } }