/* Copyright (C) 2011, Dirk Trossen, airs@dirk-trossen.de This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation as version 2.1 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.airs; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.widget.Toast; import com.airs.helper.SerialPortLogger; import com.airs.helper.Waker; import com.airs.platform.Acquisition; import com.airs.platform.Discovery; import com.airs.platform.EventComponent; import com.airs.platform.HandlerManager; /** * Service to implement the remote recording * * @see AIRS_local * @see AIRS_record_tab * @see android.app.Service */ public class AIRS_remote extends Service { private static final int BATTERY_KILL = 3; /** * Handler variable for showing a notification through the UI thread */ public static final int SHOW_NOTIFICATION = 2; private static final int KILL_SERVICE = 1; /** * Handler variable for the text shown by the notification */ public static final String TEXT = "TEXT"; private Context airs = null; private Discovery instDiscovery = null; private Acquisition instAcquisition = null; private EventComponent instEC = null; private boolean Vibrate, Lights; private int Reminder_i; private int BatteryKill_i; private String LightCode; private NotificationManager mNotificationManager; private String IPAddress; private String IPPort; /** * Flag if AIRS is recording */ public boolean running = false; /** * Flag if AIRS has been started as a service already */ public boolean started = false; /** * Flag that connection request to your own NORS application server has failed */ public boolean failed = false; // This is the object that receives interactions from clients private final IBinder mBinder = new LocalBinder(); private VibrateThread Vibrator; private Notification notification; private WakeLock wl; /** * Bytes sent to your own NORS application server during the recording */ public int bytes_sent = 0; /** * Values sent to your own NORS application server during the recording */ public int values_sent = 0; /** * Sleep function * @param millis */ private void sleep(long millis) { Waker.sleep(millis); } private void debug(String msg) { SerialPortLogger.debug(msg); } public class LocalBinder extends Binder { AIRS_remote getService() { return AIRS_remote.this; } } /** * Returns current instance of AIRS_local Service to anybody binding to AIRS_local * @param intent Reference to calling {@link android.content.Intent} * @return current instance to service */ @Override public IBinder onBind(Intent intent) { debug("RSA_remote::bound service!"); return mBinder; } /** * Called when starting the service the first time around * @see android.app.Service */ @Override public void onCreate() { } /** * Called when service is destroyed, e.g., by stopService() * Here, we tear down all recording threads, close all handlers, unregister receivers for battery signal and close the thread for indicating the recording * @see android.app.Service */ @Override public void onDestroy() { SerialPortLogger.debug("RSA_remote::destroying service..."); if (instEC!=null) instEC.stop(); SerialPortLogger.debug("RSA_remote::...terminating Vibrate thread"); // if Vibrator is running, stop it! try { // if Vibrator is running, stop it! if (Vibrator!=null) { Vibrator.stop = true; Vibrator.thread.interrupt(); } } catch(Exception e) { debug("RSA::Exception when terminating Vibrator thread!"); } try { SerialPortLogger.debug("RSA_remote::...destroy handlers"); if (started == true) HandlerManager.destroyHandlers(); } catch(Exception e) { } SerialPortLogger.debug("RSA_remote::...release wake lock"); // create wake lock if held if (wl != null) if (wl.isHeld() == true) wl.release(); SerialPortLogger.debug("RSA_remote::...onDestroy() finished"); } /** * Called when startService() is invoked by other parts of AIRS (AIRS_record_tab as well as AIRS_shortcut) * The sequence of calling this appropriately (be careful to not change this) * 1. startService() * 2. bindService() * 3. set start = true and call startService() again * 4. service.Discover() -> sets discovered == true * 5. startService() again for measurements * @param intent Reference to the calling {@link android.content.Intent} * @param flags Flags set by the caller * @param startId ID created per calling of the service (identifying the caller) * @see AIRS_shortcut * @see AIRS_record_tab */ @Override public int onStartCommand(Intent intent, int flags, int startId) { debug("RSA_remote::started service ID " + Integer.toString(startId)); // return if intent is null -> service was restarted if (intent == null) return Service.START_NOT_STICKY; // sensing running? if (running == true) return Service.START_NOT_STICKY; if (started == false) return Service.START_NOT_STICKY; // start the measurements if discovered running = startRSA(); // stop service if starting failed if (running == false) { Toast.makeText(getApplicationContext(), "Starting remote sensing failed!\nStart AIRS and try again.", Toast.LENGTH_LONG).show(); Message msg = mHandler.obtainMessage(KILL_SERVICE); mHandler.sendMessage(msg); } else Toast.makeText(getApplicationContext(), "Starting remote sensing successful!\nStart monitoring by clicking on notification bar message.", Toast.LENGTH_LONG).show(); return Service.START_NOT_STICKY; } // function blocks -> meant as server kind of @SuppressWarnings("deprecation") private boolean startRSA() { PendingIntent contentIntent; // create timer/alarm handling Waker.init(this); this.airs = this.getApplicationContext(); debug("create handlers..."); HandlerManager.createHandlers(getApplicationContext()); // started handlers started = true; // get preference variables from settings Reminder_i = HandlerManager.readRMS_i("Reminder", 0) * 1000; Vibrate = HandlerManager.readRMS_b("Vibrator", true); Lights = HandlerManager.readRMS_b("Lights", true); LightCode = HandlerManager.readRMS("LightCode", "00ff00"); IPAddress = HandlerManager.readRMS("IPStore", "127.0.0.1"); IPPort = HandlerManager.readRMS("IPPort", "9000"); // find out whether or not to kill based on battery condition BatteryKill_i = HandlerManager.readRMS_i("BatteryKill", 0); // create notification NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notification = new Notification(R.drawable.notification_icon, "Starting AIRS", System.currentTimeMillis()); // create pending intent for starting the activity contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, AIRS_remotevalues.class), Intent.FLAG_ACTIVITY_NEW_TASK); notification.setLatestEventInfo(getApplicationContext(), "AIRS Remote Sensing", "...is trying to connect since...", contentIntent); notification.flags = Notification.FLAG_NO_CLEAR; mNotificationManager.notify(0, notification); // create local event component instEC = new EventComponent(); // trying to connect if (instEC.startEC(this, IPAddress, IPPort) == true) { // create acquisition component instAcquisition = new Acquisition(instEC); // create discovery component instDiscovery = new Discovery(instEC); // any initialization failed? if (instAcquisition == null || instDiscovery==null) return false; // vibrate? if (Reminder_i>0) Vibrator = new VibrateThread(); // update notification notification = new Notification(R.drawable.notification_icon, getString(R.string.Started_AIRS), System.currentTimeMillis()); // create pending intent for starting the activity contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, AIRS_remotevalues.class), Intent.FLAG_ACTIVITY_NEW_TASK); notification.setLatestEventInfo(getApplicationContext(), getString(R.string.AIRS_Remote_Sensing), getString(R.string.running_since), contentIntent); notification.flags = Notification.FLAG_NO_CLEAR; startForeground(1, notification); // create new wakelock PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE); wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AIRS Remote Lock"); wl.acquire(); // need battery monitor? if (BatteryKill_i > 0) { // register intent for watching battery IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mReceiver, filter); } return true; } else { failed = true; return false; } } // The Handler that gets information back from the other threads, updating the values for the UI public final Handler mHandler = new Handler() { @SuppressWarnings("deprecation") @Override public void handleMessage(Message msg) { NotificationManager mNotificationManager; switch (msg.what) { case BATTERY_KILL: // stop foreground service stopForeground(true); // now create new notification mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification(R.drawable.notification_icon, getString(R.string.AIRS_killed), System.currentTimeMillis()); Intent notificationIntent = new Intent(getApplicationContext(), AIRS_tabs.class); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, Intent.FLAG_ACTIVITY_NEW_TASK); notification.setLatestEventInfo(getApplicationContext(), getString(R.string.AIRS_Remote_Sensing), getString(R.string.killed_at) + " " + Integer.toString(BatteryKill_i) + "% " + getString(R.string.battery) + "...", contentIntent); // give full fanfare notification.flags |= Notification.DEFAULT_SOUND | Notification.FLAG_AUTO_CANCEL | Notification.FLAG_SHOW_LIGHTS; notification.ledARGB = 0xffff0000; notification.ledOffMS = 1000; notification.ledOnMS = 1000; mNotificationManager.notify(17, notification); // stop service now! stopSelf(); break; case SHOW_NOTIFICATION: Toast.makeText(getApplicationContext(), msg.getData().getString(TEXT), Toast.LENGTH_LONG).show(); break; case KILL_SERVICE: // stop foreground service stopForeground(true); // stop service now! stopSelf(); // remove icon mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.cancelAll(); break; default: break; } } }; // Vibrate watchdog private class VibrateThread implements Runnable { public Thread thread; public boolean stop = false; VibrateThread() { // save thread for later to stop (thread = new Thread(this)).start(); } public void run() { long vibration[] = {0,200,0}; // get power manager PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); // get notification manager mNotificationManager = (NotificationManager)airs.getSystemService(Context.NOTIFICATION_SERVICE); while (stop == false) { // sleep for the agreed time sleep(Reminder_i); // only shows "still running" notification when screen is off if (pm.isScreenOn() == false) { // prepare notification to user if (Vibrate == true) notification.vibrate = vibration; if (Lights == true) { notification.ledARGB = 0xff000000 | Integer.valueOf(LightCode, 16); notification.flags |= Notification.FLAG_SHOW_LIGHTS; } // now shoot off alert mNotificationManager.notify(1, notification); sleep(750); // switch off vibrate and lights and update notification again notification.vibrate = null; notification.flags &= ~Notification.FLAG_SHOW_LIGHTS; mNotificationManager.notify(1, notification); } } } } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int Battery = 100; // if battery changed... if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { int rawlevel = intent.getIntExtra("level", -1); int scale = intent.getIntExtra("scale", -1); if (rawlevel >= 0 && scale > 0) Battery = (rawlevel * 100) / scale; // need to trigger battery kill action? if (Battery < BatteryKill_i) { Message msg = mHandler.obtainMessage(BATTERY_KILL); mHandler.sendMessage(msg); } } } }; }