public class ServiceSensorControl extends Service { private final Logger log = LogPrincipal.get(); // CONSTANTS public static final String SENSOR_FILENAME = "sensor.ssf"; public static final String STAGE_FILENAME = "sensor.stage.ssf"; private static final String SHARED_PREFS_NAME = "SensorCollectorPreferences"; private static final String PREF_ID = "userid"; private static final String PREF_SECRET = "user_secret"; // MAIN EXECUTION SERVICE public final ScheduledThreadPoolExecutor executorService; // STATUS FLAGS public boolean isRecording = false; public boolean isStreaming = false; public boolean isHAR = false; public String userId = ""; // will be set onCreate // COMMUNICATION CHANNEL public SensorQueue sensorQueue = new LinkedSensorQueue(); // REMARK: // Need to put initialization to onCreate, since FilesDir, etc. is not available // from a static context. // INDICES public StaticIPS staticIPS; // SENSOR CONSUMERS public Persistor persistor; public PublicationPipeline publisher; public Consumer<Item> streamer; public Consumer<Item> harPipeline; public Consumer<Item> ppsPipeline; public Consumer<Item> waitingPipeline; public GpsCache gpsCache; // THREADS public ConnectorThread connectorThread; public TransferManager transferManager; public MonitorThread monitorThread; // Rem: Also SensorThread would belong here, but it is realized via static methods /* CONSTRUCTOR */ public ServiceSensorControl() { // Register this object globally GlobalContext.set(this); // Create the executor service, keep two threads in the pool executorService = new ScheduledThreadPoolExecutor(SensorCollectionOptions.MAIN_EXECUTOR_CORE_POOL); // If feature is available, enable core thread timeout with five seconds if (Build.VERSION.SDK_INT >= 9) { executorService.setKeepAliveTime(MAIN_EXECUTOR_CORE_TIMEOUT, TimeUnit.MILLISECONDS); executorService.allowCoreThreadTimeOut(true); } } /* ANDROID LIFECYCLE */ @Override public void onCreate() { super.onCreate(); LogPrincipal.configure(); log.info("Creating ServiceSensorControl"); // INITIALIZATIONS // Warning: getFilesDir is only available after onCreate was called. File sensorFile = new File(GlobalContext.getFileRoot(), SENSOR_FILENAME); File stageFile = new File(GlobalContext.getFileRoot(), STAGE_FILENAME); // Init index staticIPS = new StaticIPS( PPSOptions.INDEX_HORIZONTAL_RESOLUTION, PPSOptions.INDEX_VERTICAL_RESOLUTION, PPSOptions.INDEX_BY_CENTROID, PPSOptions.INDEX_STORE_DEGREE, new Callable<InputStream>() { @Override public InputStream call() throws IOException { return getAssets().open(PPSOptions.HELSINKIIPPS_ASSET); } }, false, PPSOptions.HELSINKI_ID_FIELD, PPSOptions.HELSINKI_LAT_FIELD, PPSOptions.HELSINKI_LON_FIELD, PPSOptions.PROXIMITY); // Init sensor consumers final ZMQStreamer zmqStreamer = new ZMQStreamer(); streamer = zmqStreamer.itemNode; harPipeline = new HARAdapter(); ppsPipeline = new PPSAdapter("platform", staticIPS); waitingPipeline = new WaitingAdapter("platform", WaitingOptions.WAITING_TRESHOLD); gpsCache = new GpsCache(); publisher = new PublicationPipeline(); // for external communication // Serialization used for persisting items Function<Item, String> persistorSerialization = JSON_PERSISTOR ? Persistor.JSON_SERIALIZATION : Persistor.REGULAR_SERIALIZATION; // Persistor taking and storing the items persistor = ZIPPED_PERSISTOR ? new ZipFilePersistor(sensorFile, persistorSerialization) : new FilePersistor(sensorFile, persistorSerialization); // INIT THREADS connectorThread = new ConnectorThread(sensorQueue); transferManager = INTENT_TRANSFER ? new IntentTransfer(persistor, GlobalContext.getFileRoot()) : new TransferThreadPost(persistor, stageFile); monitorThread = new MonitorThread(); // Restore user id from shared preferences restoreUserId(); // Setup sensor thread SensorThread.setup(sensorQueue); final int recordingNotificationId = 1; // Start Recording once the first consumers connects to connector thread. // This should be done once the SensorThread is already running. connectorThread.nonEmpty.register( new Callback<Consumer<? super Item>>() { @Override public void call(Consumer<? super Item> consumer) { log.debug("Start recording sensors, configuration " + SensorCollectionOptions.con()); // Notification NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new NotificationCompat.Builder(ServiceSensorControl.this) .setContentTitle("Sensor miner") .setContentText("Recording sensor data") .setSmallIcon(R.drawable.ic_launcher) .setLights(0xff0000ff, 900, 900) .setOngoing(true) .setProgress(0, 0, true) .build(); notificationManager.notify(recordingNotificationId, notification); SensorThread.startAllRecording(); } }); connectorThread.empty.register( new Callback<Consumer<? super Item>>() { @Override public void call(Consumer<? super Item> consumer) { log.debug("Stop recording sensors"); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(recordingNotificationId); SensorThread.stopAllRecording(); } }); // Setup monitoring thread monitorThread.registerMonitorable(connectorThread, "SampleCount"); monitorThread.registerMonitorable(persistor, "Persitor"); monitorThread.registerMonitorable(transferManager, "Transfer"); monitorThread.registerMonitorable(sensorQueue, "Queue"); // Start threads connectorThread.start(); monitorThread.start(); SensorThread.start(); } @Override public void onDestroy() { log.debug("Called onDestroy()"); persistor.close(); executorService.shutdown(); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } /* INTENT API */ /** * Dispatches incoming intents. See {@link * eu.liveandgov.wp1.sensor_collector.configuration.IntentAPI} for valid intents. */ @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { log.debug("No intent received."); return START_STICKY; } String action = intent.getAction(); log.debug("Received intent with action " + action); if (action == null) return START_STICKY; // Dispatch IntentAPI if (action.equals(IntentAPI.ACTION_RECORDING_ENABLE)) { doEnableRecording(); doSendStatus(); } else if (action.equals(IntentAPI.RECORDING_DISABLE)) { doDisableRecording(); doSendStatus(); } else if (action.equals(IntentAPI.ACTION_TRANSFER_SAMPLES)) { doTransferSamples(); doSendStatus(); } else if (action.equals(IntentAPI.ACTION_ANNOTATE)) { doAnnotate(intent.getStringExtra(IntentAPI.FIELD_ANNOTATION)); } else if (action.equals(IntentAPI.ACTION_GET_STATUS)) { doSendStatus(); } else if (action.equals(IntentAPI.ACTION_START_HAR)) { doStartHAR(); } else if (action.equals(IntentAPI.ACTION_STOP_HAR)) { doStopHAR(); } else if (action.equals(ExtendedIntentAPI.START_STREAMING)) { doStartStreaming(); } else if (action.equals(ExtendedIntentAPI.STOP_STREAMING)) { doStopStreaming(); } else if (action.equals(IntentAPI.ACTION_SET_ID)) { doSetId(intent.getStringExtra(IntentAPI.FIELD_USER_ID)); } else if (action.equals(ExtendedIntentAPI.ACTION_GET_GPS)) { doSendGps(); } else if (action.equals(ExtendedIntentAPI.ACTION_DELETE_SAMPLES)) { doDeleteSamples(); } else { log.debug("Received unknown action " + action); } return START_STICKY; } private void doDeleteSamples() { persistor.deleteSamples(); publisher.deleteSamples(); transferManager.deleteStagedSamples(); } private void doStopHAR() { connectorThread.removeConsumer(harPipeline); connectorThread.removeConsumer(ppsPipeline); connectorThread.removeConsumer(waitingPipeline); isHAR = false; // On har-stop, save indices staticIPS.trySave(new File(getFilesDir(), PPSOptions.HELSINKIIPPS_INDEX_FILE)); } private void doStartHAR() { // On har-start, load indices staticIPS.tryLoad(new File(getFilesDir(), PPSOptions.HELSINKIIPPS_INDEX_FILE)); isHAR = true; connectorThread.addConsumer(waitingPipeline); connectorThread.addConsumer(ppsPipeline); connectorThread.addConsumer(harPipeline); } private void doStopStreaming() { isStreaming = false; connectorThread.removeConsumer(streamer); } private void doStartStreaming() { isStreaming = true; connectorThread.addConsumer(streamer); } private void doSetId(String id) { log.debug("Setting userId to:" + id); userId = id; String userSecret = RandomStringUtils.randomAlphanumeric(5); log.debug("Created new user Secret: " + userSecret); // Update Shared Preferences SharedPreferences settings = getSharedPreferences(SHARED_PREFS_NAME, 0); if (settings == null) throw new IllegalStateException("Failed to load SharedPreferences"); SharedPreferences.Editor editor = settings.edit(); editor.putString(PREF_ID, id); editor.putString(PREF_SECRET, userSecret); editor.commit(); doAnnotate("USER_ID SET TO: " + id); } private void doAnnotate(String tag) { log.debug("Adding annotation:" + tag); sensorQueue.push(new Tag(System.currentTimeMillis(), GlobalContext.getUserId(), tag)); } private void doTransferSamples() { transferManager.doTransfer(); } private void doDisableRecording() { connectorThread.removeConsumer(persistor); isRecording = false; final Tag stopRecordingTag = new Tag( System.currentTimeMillis(), GlobalContext.getUserId(), IntentAPI.VALUE_STOP_RECORDING); persistor.push(stopRecordingTag); // API EXTENSIONS are triggered on together with recording if (API_EXTENSIONS) { // Add "STOP RECORDING TAG" to publisher publisher.push(stopRecordingTag); connectorThread.removeConsumer(publisher); connectorThread.removeConsumer(gpsCache); } } private void doEnableRecording() { connectorThread.addConsumer(persistor); isRecording = true; final Tag tagStartRecording = new Tag( System.currentTimeMillis(), GlobalContext.getUserId(), IntentAPI.VALUE_START_RECORDING); persistor.push(tagStartRecording); // API EXTENSIONS are triggered on together with recording if (API_EXTENSIONS) { publisher.push(tagStartRecording); connectorThread.addConsumer(publisher); connectorThread.addConsumer(gpsCache); } } public void doSendStatus() { Intent intent = new Intent(IntentAPI.RETURN_STATUS); intent.putExtra(IntentAPI.FIELD_SAMPLING, isRecording); intent.putExtra(IntentAPI.FIELD_TRANSFERRING, transferManager.isTransferring()); intent.putExtra( IntentAPI.FIELD_SAMPLES_STORED, persistor.hasSamples() | transferManager.hasStagedSamples()); intent.putExtra(ExtendedIntentAPI.FIELD_STREAMING, isStreaming); intent.putExtra(IntentAPI.FIELD_HAR, isHAR); intent.putExtra(IntentAPI.FIELD_USER_ID, userId); sendBroadcast(intent); } private void doSendGps() { if (gpsCache == null) log.warn("gpsCache not initialized!"); Intent intent = new Intent(ExtendedIntentAPI.RETURN_GPS_SAMPLES); intent.putExtra(ExtendedIntentAPI.FIELD_GPS_ENTRIES, gpsCache.getEntryString()); sendBroadcast(intent); log.debug("Sent gps message " + gpsCache.getEntryString()); } // HELPER METHODS /** Restore UserId from SharedPreferences. Uses AndoridID if no Id is found. */ private void restoreUserId() { String androidId = Settings.Secure.getString( GlobalContext.context.getContentResolver(), Settings.Secure.ANDROID_ID); // Restore preferences SharedPreferences settings = getSharedPreferences(SHARED_PREFS_NAME, 0); if (settings == null) throw new IllegalStateException("Failed to load SharedPreferences"); userId = settings.getString(PREF_ID, androidId); // use androidId as default; } }
/* ANDROID LIFECYCLE */ @Override public void onCreate() { super.onCreate(); LogPrincipal.configure(); log.info("Creating ServiceSensorControl"); // INITIALIZATIONS // Warning: getFilesDir is only available after onCreate was called. File sensorFile = new File(GlobalContext.getFileRoot(), SENSOR_FILENAME); File stageFile = new File(GlobalContext.getFileRoot(), STAGE_FILENAME); // Init index staticIPS = new StaticIPS( PPSOptions.INDEX_HORIZONTAL_RESOLUTION, PPSOptions.INDEX_VERTICAL_RESOLUTION, PPSOptions.INDEX_BY_CENTROID, PPSOptions.INDEX_STORE_DEGREE, new Callable<InputStream>() { @Override public InputStream call() throws IOException { return getAssets().open(PPSOptions.HELSINKIIPPS_ASSET); } }, false, PPSOptions.HELSINKI_ID_FIELD, PPSOptions.HELSINKI_LAT_FIELD, PPSOptions.HELSINKI_LON_FIELD, PPSOptions.PROXIMITY); // Init sensor consumers final ZMQStreamer zmqStreamer = new ZMQStreamer(); streamer = zmqStreamer.itemNode; harPipeline = new HARAdapter(); ppsPipeline = new PPSAdapter("platform", staticIPS); waitingPipeline = new WaitingAdapter("platform", WaitingOptions.WAITING_TRESHOLD); gpsCache = new GpsCache(); publisher = new PublicationPipeline(); // for external communication // Serialization used for persisting items Function<Item, String> persistorSerialization = JSON_PERSISTOR ? Persistor.JSON_SERIALIZATION : Persistor.REGULAR_SERIALIZATION; // Persistor taking and storing the items persistor = ZIPPED_PERSISTOR ? new ZipFilePersistor(sensorFile, persistorSerialization) : new FilePersistor(sensorFile, persistorSerialization); // INIT THREADS connectorThread = new ConnectorThread(sensorQueue); transferManager = INTENT_TRANSFER ? new IntentTransfer(persistor, GlobalContext.getFileRoot()) : new TransferThreadPost(persistor, stageFile); monitorThread = new MonitorThread(); // Restore user id from shared preferences restoreUserId(); // Setup sensor thread SensorThread.setup(sensorQueue); final int recordingNotificationId = 1; // Start Recording once the first consumers connects to connector thread. // This should be done once the SensorThread is already running. connectorThread.nonEmpty.register( new Callback<Consumer<? super Item>>() { @Override public void call(Consumer<? super Item> consumer) { log.debug("Start recording sensors, configuration " + SensorCollectionOptions.con()); // Notification NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new NotificationCompat.Builder(ServiceSensorControl.this) .setContentTitle("Sensor miner") .setContentText("Recording sensor data") .setSmallIcon(R.drawable.ic_launcher) .setLights(0xff0000ff, 900, 900) .setOngoing(true) .setProgress(0, 0, true) .build(); notificationManager.notify(recordingNotificationId, notification); SensorThread.startAllRecording(); } }); connectorThread.empty.register( new Callback<Consumer<? super Item>>() { @Override public void call(Consumer<? super Item> consumer) { log.debug("Stop recording sensors"); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(recordingNotificationId); SensorThread.stopAllRecording(); } }); // Setup monitoring thread monitorThread.registerMonitorable(connectorThread, "SampleCount"); monitorThread.registerMonitorable(persistor, "Persitor"); monitorThread.registerMonitorable(transferManager, "Transfer"); monitorThread.registerMonitorable(sensorQueue, "Queue"); // Start threads connectorThread.start(); monitorThread.start(); SensorThread.start(); }