/* * Copyright (C) 2012, Katy Hilgenberg. * Special acknowledgments to: Knowledge & Data Engineering Group, University of Kassel (http://www.kde.cs.uni-kassel.de). * Contact: sdcf@cs.uni-kassel.de * * This file is part of the SDCFramework (Sensor Data Collection Framework) project. * * The SDCFramework 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, either version 3 of the License, or * (at your option) any later version. * * The SDCFramework 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 the SDCFramework. If not, see <http://www.gnu.org/licenses/>. */ package eu.liveandgov.sensorcollector; import java.util.concurrent.atomic.AtomicBoolean; import eu.liveandgov.sensorcollector.R; import de.unikassel.android.sdcframework.app.AbstractServiceControlActivity; import de.unikassel.android.sdcframework.app.SDCFileBrowserActivity; import de.unikassel.android.sdcframework.app.SDCServiceController; import de.unikassel.android.sdcframework.app.facade.ISDCService; import de.unikassel.android.sdcframework.provider.TagProviderData; import de.unikassel.android.sdcframework.provider.facade.ContentProviderData; import de.unikassel.android.sdcframework.service.SDCServiceConnectionHolder; import de.unikassel.android.sdcframework.service.ServiceUtils; import de.unikassel.android.sdcframework.util.LogEvent; import de.unikassel.android.sdcframework.util.Logger; import de.unikassel.android.sdcframework.util.TimeProvider; import de.unikassel.android.sdcframework.util.facade.BroadcastableEvent; import de.unikassel.android.sdcframework.util.facade.EventObserver; import de.unikassel.android.sdcframework.util.facade.LogLevel; import de.unikassel.android.sdcframework.util.facade.ObservableEventSource; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.app.AlertDialog; import android.content.ContentValues; import android.content.DialogInterface; import android.text.Selection; import android.text.Spannable; import android.text.SpannableString; import android.text.method.ScrollingMovementMethod; import android.text.style.ForegroundColorSpan; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; /** * * Live+Gov Sensor Collection Application * * * Builds upon the embedded SDCF application example. It does replace the * internal SDCF controller activity by extending the type * AbstractServiceControlActivity. * * * @author Katy Hilgenberg, Heinrich Hartmann, Richard Schütz * */ public class ControlActivity extends AbstractServiceControlActivity implements SDCServiceConnectionHolder.ServiceConnectionEventReceiver, EventObserver<LogEvent> { /** * The SDC service connection holder */ private final SDCServiceConnectionHolder connectionHolder; /** * Start/stop service */ private ToggleButton toggleServiceStateButton; /** * Start/stop recording */ private ToggleButton toggleRecordingStateButton; private ProgressBar recordingSpinner; /** * Start sample transfer */ private Button transferButton; private ProgressBar transferSpinner; /** * Annotations */ private Button annotateButton; private EditText annotationTextField; private Spinner annodationDropDown; /** * Event Logging */ private final Handler logEventHandler; private TextView logView; /** * Recording state flag */ private final AtomicBoolean isRecording; /** * Service state flag */ private Boolean isRunning; /** * Transfering */ private Boolean isTransferingData; /** * Reference to a running service when bound */ private ISDCService service; /** * Constructor */ public ControlActivity() { super(ActivityConstants.serviceClass); this.connectionHolder = new SDCServiceConnectionHolder(this, ActivityConstants.serviceClass); isTransferingData = false; this.isRecording = new AtomicBoolean(false); isRunning = false; /** * Create Logging Handler */ logEventHandler = new Handler() { public void handleMessage(Message msg) { try { if (msg.obj instanceof BroadcastableEvent) { LogEvent event = (LogEvent) msg.obj; handleLogEvent(event); } } catch (Exception e) { Logger.getInstance().error(ControlActivity.this, "Exception in handleMessage"); e.printStackTrace(); } } }; } /** * On Create Service */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /********************************* * Register UI Elements *********************************/ /** * Start/Stop Service */ toggleServiceStateButton = (ToggleButton) findViewById(R.id.startStopServiceButton); toggleServiceStateButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { setRunningFlag(); if (ControlActivity.this.isRunning) { doStopService(); } else { doStartService(); } } }); /** * Toggle Recording */ toggleRecordingStateButton = (ToggleButton) findViewById(R.id.toggleRecordingButton); toggleRecordingStateButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onToggleRecordingState(); } }); recordingSpinner = (ProgressBar) findViewById(R.id.progressSpinner); recordingSpinner.setVisibility(View.INVISIBLE); /** * Toggle Transfer */ transferButton = (Button) findViewById(R.id.transferSamplesButton); transferButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { triggerSampleTransfer(); } }); transferSpinner = (ProgressBar) findViewById(R.id.trasnferSpinner); /** * Annotations */ annotationTextField = (EditText) findViewById(R.id.annotationTextfield); annotationTextField.setEnabled(false); annotateButton = (Button) findViewById(R.id.sendAnnotationButton); annotateButton.setEnabled(false); annodationDropDown = (Spinner) findViewById(R.id.dropdownAnnotation); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource( this, R.array.movements_array, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); annodationDropDown.setAdapter(adapter); annodationDropDown .setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (ControlActivity.this.isRecording.get()) { String annotation = annodationDropDown .getSelectedItem().toString(); sendAnnotationToSDCF("Activity: " + annotation); } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); annodationDropDown.setEnabled(false); updateButtons(); /** * Logging */ logView = (TextView) findViewById(R.id.sdcfLogview); logView.setSingleLine(false); logView.setMovementMethod(new ScrollingMovementMethod()); Logger.getInstance().setLogLevel(LogLevel.DEBUG); Logger.getInstance().registerEventObserver(this); } /** * On Service Resume */ @Override protected void onResume() { super.onResume(); Logger.getInstance().registerEventObserver(this); setRunningFlag(); updateButtons(); if (!ServiceUtils.isServiceAvailable(this, ActivityConstants.serviceClass)) { AlertDialog alertDialog = new AlertDialog.Builder(this).create(); alertDialog.setTitle("Important"); alertDialog.setMessage("The SDCF Service is not installed!"); alertDialog.setButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }); alertDialog.setIcon(R.drawable.ic_launcher); alertDialog.show(); } if (isRunning) { connectionHolder.onCreate(this); } } /* * (non-Javadoc) * * @see android.app.Activity#onPause() */ @Override protected void onPause() { super.onPause(); Logger.getInstance().unregisterEventObserver(this); } /* * (non-Javadoc) * * @see android.app.Activity#onDestroy() */ @Override protected void onDestroy() { super.onDestroy(); if (ServiceUtils.isServiceRunning(getApplicationContext(), ActivityConstants.serviceClass)) { connectionHolder.onDestroy(this); } } /************************************************************** * Core Functions **************************************************************/ /** * Read Annotations from UI and send to the service */ private void sendAnnotation() { String annotation = annodationDropDown.getSelectedItem().toString(); sendAnnotationToSDCF("Activity: " + annotation); annotation = annotationTextField.getText().toString(); if (annotation != null && annotation.length() != 0) { // store annotation text as tag sensor information sendAnnotationToSDCF(annotation); } } /** * Method to start the service */ protected void doStartService() { ServiceUtils.startService(getApplicationContext(), ActivityConstants.serviceClass.getName()); connectionHolder.onCreate(this); Logger.getInstance().registerEventObserver(this); } /** * Method to stop the service */ protected void doStopService() { connectionHolder.onDestroy(this); ServiceUtils.stopService(getApplicationContext(), ActivityConstants.serviceClass.getName()); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.app.AbstractServiceControlActivity# * onServiceRunningStateChanged(boolean) */ @Override protected void onServiceRunningStateChanged(boolean isRunning) { setRunningFlag(); updateButtons(); } /** * Handler for recording state button events. */ protected void onToggleRecordingState() { if (toggleRecordingStateButton.isChecked()) { // Recording is checked now // Disable Sample Transfer? // enableSampleTransfer( false ); // Enable sample Recording! startRecording(); updateButtons(); sendAnnotation(); } else if (isRecording.get()) { // Recording is not checked and service is running // Stop sample recording stopRecording(); // Transfer samples triggerSampleTransfer(); updateButtons(); } } public void onAnnotateClick(View view) { sendAnnotation(); } /************************************************************* * SDCF Connection *************************************************************/ /** * Method to send an annotation as tag sensor information to the SDC * service. * * @param tag * the tag value to write */ private void sendAnnotationToSDCF(String tag) { ContentValues values = new ContentValues(); // IMPORTANT: The time stamp is internally overridden by the service // which does generate its own sample time stamp when removing tag data. // To guarantee a reliable time stamp, the service should run when // adding annotation information that samples will be processed in time. values.put(ContentProviderData.TIMESTAMP, Long.toString(TimeProvider.getInstance().getTimeStamp())); values.put(TagProviderData.TEXT, tag); getContentResolver().insert( TagProviderData.getInstance().getContentUri(), values); Logger.getInstance().info(this, "Annotaion Added: " + tag); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.service.SDCServiceConnectionHolder. * ServiceConnectionEventReceiver * #onConnectionEstablished(de.unikassel.android. * sdcframework.app.facade.ISDCService) */ @Override public void onConnectionEstablished(ISDCService sdcService) { setService(sdcService); // do initialization tasks when the binding is established enableSampling(false); //enableSampleTransfer(false); //enableSampleBroadcasts(true); enableSampleStorage(true); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.service.SDCServiceConnectionHolder. * ServiceConnectionEventReceiver * #onAboutToDisconnect(de.unikassel.android.sdcframework * .app.facade.ISDCService) */ @Override public void onAboutToDisconnect(ISDCService sdcService) { // do cleanup tasks when then binding is gone toggleRecordingStateButton.setChecked(false); enableSampleBroadcasts(false); setService(null); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.service.SDCServiceConnectionHolder. * ServiceConnectionEventReceiver#onConnectionLost() */ @Override public void onConnectionLost() { } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.service.SDCServiceConnectionHolder. * ServiceConnectionEventReceiver#onBindingFailed() */ @Override public void onBindingFailed() { // should normally not happen if the service manifest configuration is // valid } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.service.SDCServiceConnectionHolder. * ServiceConnectionEventReceiver#onServiceUnavailable() */ @Override public void onServiceUnavailable() { // Ignore for applications with embedded SDCF service } /** * Method to enable/disable the sample transfer feature. * * @param enabledState * true to enable the feature, false to disable it */ public boolean enableSampleTransfer(boolean enabledState) { try { getService().doEnableSampleTransfer(enabledState); return true; } catch (RemoteException e) { } catch (NullPointerException e) { } return false; } /** * Method to enable/disable the sample storage feature. * * @param enabledState * true to enable the feature, false to disable it */ public boolean enableSampleStorage(boolean enabledState) { try { getService().doEnableSampleStorage(enabledState); return true; } catch (RemoteException e) { } catch (NullPointerException e) { } return false; } /** * Method to enable/disable the sampling feature. * * @param enabledState * true to enable the feature, false to disable it */ public boolean enableSampling(boolean enabledState) { try { getService().doEnableSampling(enabledState); return true; } catch (RemoteException e) { } catch (NullPointerException e) { } return false; } /** * Method to enable/disable the service sample broadcast feature. * * @param enabledState * true to enable the feature, false to disable it */ public boolean enableSampleBroadcasts(boolean enabledState) { try { getService().doEnableSampleBroadCasting(enabledState); return true; } catch (RemoteException e) { } catch (NullPointerException e) { } return false; } /** * Method to force the activation and transfer of samples */ public boolean triggerSampleTransfer() { Logger.getInstance().info(this, "Starting sample transfer."); try { getService().doTriggerSampleTransfer(); return true; } catch (RemoteException e) { } catch (NullPointerException e) { } return false; } /** * Getter for the text view * * @return the text view for logging */ private TextView getLogView() { return logView; } /** * Method to handle for log events * * @param logEvent * the log Event */ private void handleLogEvent(LogEvent logEvent) { TextView logView = getLogView(); String message = logEvent.getMessage(); logView.append(message); logView.append("\n"); if(message.startsWith("TransferManagerImpl: Preparation started") || message.startsWith("TransferManagerImpl: Transmission started")){ isTransferingData = true; updateButtons(); } else if (message.startsWith("TransferManagerImpl: Transmission ended")){ isTransferingData = false; updateButtons(); } // scroll to end logView.setSelected(true); Spannable textDisplayed = (Spannable) logView.getText(); Selection.setSelection(textDisplayed, textDisplayed.length()); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.util.facade.EventObserver#onEvent(de. * unikassel.android.sdcframework.util.facade.ObservableEventSource, * de.unikassel.android.sdcframework.util.facade.ObservableEvent) */ @Override public void onEvent(ObservableEventSource<? extends LogEvent> eventSource, LogEvent observedEvent) { Message msg = Message.obtain(); msg.obj = observedEvent; msg.setTarget(logEventHandler); msg.sendToTarget(); } protected synchronized void setService(ISDCService service) { this.service = service; } protected synchronized ISDCService getService() { return service; } /*************************************************************** * Options Menu * ***************************************************************/ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } /* * (non-Javadoc) * * @see android.app.Activity#onPrepareOptionsMenu(android.view.Menu) */ @Override public boolean onPrepareOptionsMenu(Menu menu) { boolean isServiceRunning = ServiceUtils.isServiceRunning( getApplicationContext(), getServiceClass()); MenuItem menuItem = menu.findItem(R.id.action_disable_transfer); if (menuItem != null) { menuItem.setEnabled(isServiceRunning); } menuItem = menu.findItem(R.id.action_force_transfer); if (menuItem != null) { menuItem.setEnabled(isServiceRunning); } return super.onPrepareOptionsMenu(menu); } /* * (non-Javadoc) * * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_disable_transfer) { return enableSampleTransfer(false); } else if (itemId == R.id.action_force_transfer) { return enableSampleTransfer(true) && triggerSampleTransfer(); } return super.onOptionsItemSelected(item); } /********************************************** * HELPER **********************************************/ /** * Set buttons to correct state Assumes isRunning and isRecording is * properly set */ private void updateButtons() { if (!isRunning) { // Service Stopped toggleServiceStateButton.setEnabled(true); toggleServiceStateButton.setChecked(false); toggleRecordingStateButton.setEnabled(false); toggleRecordingStateButton.setChecked(false); transferButton.setEnabled(false); transferSpinner.setVisibility(View.INVISIBLE); annodationDropDown.setEnabled(false); annotationTextField.setEnabled(false); annotateButton.setEnabled(false); recordingSpinner.setVisibility(View.INVISIBLE); } else { if (!isRecording.get()) { // Service Running and not recording samples toggleServiceStateButton.setEnabled(true); toggleServiceStateButton.setChecked(true); toggleRecordingStateButton.setEnabled(true); toggleRecordingStateButton.setChecked(false); transferButton.setEnabled(true); annodationDropDown.setEnabled(false); annotationTextField.setEnabled(false); annotateButton.setEnabled(false); recordingSpinner.setVisibility(View.INVISIBLE); } else { // Service Running and recording samples toggleServiceStateButton.setEnabled(false); toggleServiceStateButton.setChecked(true); toggleRecordingStateButton.setEnabled(true); toggleRecordingStateButton.setChecked(true); annodationDropDown.setEnabled(true); annotationTextField.setEnabled(true); annotateButton.setEnabled(true); recordingSpinner.setVisibility(View.VISIBLE); } if (isTransferingData){ transferSpinner.setVisibility(View.VISIBLE); } else { transferSpinner.setVisibility(View.INVISIBLE); } } } private void setRunningFlag() { isRunning = ServiceUtils.isServiceRunning(getApplicationContext(), ActivityConstants.serviceClass); } private void startRecording() { enableSampling(true); isRecording.set(true); } private void stopRecording() { enableSampling(false); isRecording.set(false); } }