/*
* 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 de.unikassel.android.sdcframework.app;
import java.io.File;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.util.SparseIntArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import de.unikassel.android.sdcframework.R;
import de.unikassel.android.sdcframework.app.facade.ISDCService;
import de.unikassel.android.sdcframework.app.facade.SDCService;
import de.unikassel.android.sdcframework.preferences.ApplicationPreferenceManagerImpl;
import de.unikassel.android.sdcframework.preferences.facade.ApplicationPreferenceManager;
import de.unikassel.android.sdcframework.provider.AudioProviderData;
import de.unikassel.android.sdcframework.provider.TwitterProviderData;
import de.unikassel.android.sdcframework.service.ServiceRunningStateListener;
import de.unikassel.android.sdcframework.service.ServiceUtils;
import de.unikassel.android.sdcframework.util.FileUtils;
import de.unikassel.android.sdcframework.util.LogEvent;
import de.unikassel.android.sdcframework.util.LogfileManager;
import de.unikassel.android.sdcframework.util.Logger;
import de.unikassel.android.sdcframework.util.facade.BroadcastableEvent;
import de.unikassel.android.sdcframework.util.facade.Encryption;
import de.unikassel.android.sdcframework.util.facade.EventObserver;
import de.unikassel.android.sdcframework.util.facade.LogLevel;
import de.unikassel.android.sdcframework.util.facade.ObservableEventSource;
/**
* The main controller activity for the sensor data collection service. It does
* provide
* <ul>
* <li>a view to start and stop the {@linkplain SDCServiceImpl},</li>
* <li>an option menu to start the {@linkplain SDCPreferenceActivity preference
* activity } for configuration,</li>
* <li>and a text view to display {@linkplain LogEvent log event messages}.</li>
* </ul>
*
* @author Katy Hilgenberg
*
*/
@SuppressLint( "Registered" )
public final class SDCServiceController
extends Activity
implements EventObserver< LogEvent >
{
/**
* Title for RSA key file selection.
*/
private static final String TITLE_RSA_KEY_SELECTION = "Select RSA Public Key File";
/**
* Title for XML configuration key file selection.
*/
private static final String TITLE_XML_CONFIG_FILE_SELECTION = "Select XML Configuration File";
/**
* File selection dialog identifier for pub key file selection.
*/
private static final int SELECT_EXT_RSA_PUBKEY_FILE = 0;
/**
* File selection dialog identifier for external defaults file selection.
*/
private static final int SELECT_EXT_DEFAULTS_FILE = 1;
/**
* The service class name
*/
private final Class< ? > serviceClass = ISDCService.class;
/**
* The service action name
*/
private final String action = SDCService.ACTION;
/**
* The event handler to handle log event messages
*/
private final Handler eventHandler;
/**
* The preference manager
*/
private final ApplicationPreferenceManager prefManager;
/**
* the start service button listener
*/
private final OnClickListener startListener;
/**
* the stop service button listener
*/
private final OnClickListener stopListener;
/**
* The text color map
*/
private final SparseIntArray textColorMap;
/**
* The text view for logging
*/
private TextView logView;
/**
* The service running state listener
*/
private final ServiceRunningStateListener serviceRunningStateListner;
/**
* Constructor
*/
public SDCServiceController()
{
super();
eventHandler = 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( SDCServiceController.this,
"Exception in handleMessage" );
e.printStackTrace();
}
}
};
this.prefManager = new ApplicationPreferenceManagerImpl();
this.startListener = new OnClickListener()
{
/*
* (non-Javadoc)
*
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick( View v )
{
// start the sensor data collection service
Logger.getInstance().registerEventObserver( SDCServiceController.this );
ServiceUtils.startService( getApplicationContext(), serviceClass );
}
};
this.stopListener = new OnClickListener()
{
/*
* (non-Javadoc)
*
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick( View v )
{
// stop the running service ( can fail if other activities are still
// bounded to it )
ServiceUtils.stopService( SDCServiceController.this, serviceClass );
}
};
this.serviceRunningStateListner = new ServiceRunningStateListener( action )
{
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.service.ServiceRunningStateListener
* #serviceStateChanged(boolean)
*/
@Override
protected void serviceStateChanged( boolean isRunning )
{
updateButtons( isRunning );
if ( isRunning == false )
{
Logger.getInstance().unregisterEventObserver(
SDCServiceController.this );
}
}
};
this.textColorMap = new SparseIntArray();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
@Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.sdc_service_controller );
// fill text color map with resource data
Resources res = getResources();
textColorMap.put( LogLevel.ERROR.ordinal(),
res.getColor( R.color.error_color ) );
textColorMap.put( LogLevel.INFO.ordinal(),
res.getColor( R.color.info_color ) );
textColorMap.put( LogLevel.WARNING.ordinal(),
res.getColor( R.color.warning_color ) );
textColorMap.put( LogLevel.DEBUG.ordinal(),
res.getColor( R.color.debug_color ) );
// add button listener
Button button = (Button) findViewById( R.id.start_button );
button.setOnClickListener( startListener );
button = (Button) findViewById( R.id.stop_button );
button.setOnClickListener( stopListener );
// configure log view
getLogView();
}
/**
* Does change Button states depending on service running state
*
* @param serviceIsRunning
* flag if the service is Running
*/
private void updateButtons( boolean serviceIsRunning )
{
Button button = (Button) findViewById( R.id.start_button );
button.setEnabled( !serviceIsRunning );
button = (Button) findViewById( R.id.stop_button );
button.setEnabled( serviceIsRunning );
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
*/
@Override
public boolean onCreateOptionsMenu( Menu menu )
{
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.optionmenu, menu );
return true;
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onPrepareOptionsMenu(android.view.Menu)
*/
@Override
public boolean onPrepareOptionsMenu( Menu menu )
{
boolean isServiceRunning =
ServiceUtils.isServiceRunning( getApplicationContext(),
serviceClass );
MenuItem menuItem = menu.findItem( R.id.clearDB );
if( menuItem != null ) menuItem.setEnabled( !isServiceRunning );
menuItem = menu.findItem( R.id.externalConfiguration );
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 )
{
boolean result = super.onOptionsItemSelected( item );
int itemId = item.getItemId();
if ( itemId == R.id.preferences )
{
result = onPreferences();
}
else if ( itemId == R.id.clearlog )
{
result = onClearLog();
}
else if ( itemId == R.id.clearDB )
{
result = onClearDB();
}
else if ( itemId == R.id.loadDefaults )
{
result = onLoadDefaults();
}
else if ( itemId == R.id.loadPublicKey )
{
result = onLoadRSAPublicKey();
}
return result;
}
/**
* Method to handle the selection of "load external defaults" in the option
* menu
*
* @return true if successful, false otherwise
*/
private boolean onLoadRSAPublicKey()
{
Intent intent = new Intent( this, SDCFileBrowserActivity.class );
intent.putExtra( SDCFileBrowserActivity.TITLE, TITLE_RSA_KEY_SELECTION );
intent.putExtra( SDCFileBrowserActivity.STARTDIR,
Environment.getExternalStorageDirectory().getAbsolutePath() );
intent.putExtra( SDCFileBrowserActivity.PATTERN, "\\.*\\.key" );
startActivityForResult( intent, SELECT_EXT_RSA_PUBKEY_FILE );
return true;
}
/**
* Method to handle the selection of "load external defaults" in the option
* menu.
*
* @return true if successful, false otherwise
*/
private boolean onLoadDefaults()
{
Intent intent = new Intent( this, SDCFileBrowserActivity.class );
intent.putExtra( SDCFileBrowserActivity.TITLE, TITLE_XML_CONFIG_FILE_SELECTION );
intent.putExtra( SDCFileBrowserActivity.STARTDIR,
Environment.getExternalStorageDirectory().getAbsolutePath() );
intent.putExtra( SDCFileBrowserActivity.PATTERN, "\\.*\\.xml" );
startActivityForResult( intent, SELECT_EXT_DEFAULTS_FILE );
return true;
}
/**
* Method to handle the selection of "clear database" in the option menu
*
* @return true if successful, false otherwise
*/
private boolean onClearDB()
{
ContentResolver contentResolver =
getApplicationContext().getContentResolver();
contentResolver.delete( TwitterProviderData.getInstance().getContentUri(),
null, null );
contentResolver.delete( AudioProviderData.getInstance().getContentUri(),
null, null );
String dbName =
getText( R.string.sdc_database_name ).toString();
return deleteDatabase( dbName );
}
/**
* Method to handle selection of "clear log" in the option menu
*
* @return true if successful, false otherwise
*/
private boolean onClearLog()
{
getLogView().setText( "" );
LogfileManager.clearAllLogs();
return true;
}
/**
* Method to handle for selection of "preferences" in the option menu
*
* @return true if successful, false otherwise
*/
private boolean onPreferences()
{
Intent intent = new Intent( this, SDCPreferenceActivity.class );
startActivity( intent );
return true;
}
/**
* Getter for the text view
*
* @return the text view for logging
*/
private TextView getLogView()
{
if ( logView == null )
{
setLogView( (TextView) findViewById( R.id.sdc_logview ) );
logView.setSingleLine( false );
logView.setMovementMethod( new ScrollingMovementMethod() );
}
return logView;
}
/**
* Setter for the logView
*
* @param logView
* the logView to set
*/
private void setLogView( TextView logView )
{
this.logView = logView;
}
/**
* Method to handle for log events
*
* @param logEvent
* the log Event
*/
private void handleLogEvent( LogEvent logEvent )
{
TextView logView = getLogView();
// determine color
LogLevel logLevel = logEvent.getLogLevel();
int color = textColorMap.get( logLevel.ordinal() );
SpannableString text = SpannableString.valueOf( logEvent.toString() );
text.setSpan( new ForegroundColorSpan( color ), 0, text.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE );
logView.append( text );
logView.append( "\n" );
// scroll to end
logView.setSelected( true );
Spannable textDisplayed = (Spannable) logView.getText();
Selection.setSelection( textDisplayed, textDisplayed.length() );
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onResume()
*/
@Override
protected void onResume()
{
// register ourself as observable of the broadcast logListener
Logger.getInstance().registerEventObserver( this );
IntentFilter filter = new IntentFilter();
filter.addAction( SDCService.ACTION );
getApplicationContext().registerReceiver( serviceRunningStateListner,
filter );
// update button states
boolean serviceIsRunning =
ServiceUtils.isServiceRunning( getApplicationContext(),
serviceClass );
updateButtons( serviceIsRunning );
super.onResume();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onPause()
*/
@Override
protected void onPause()
{
getApplicationContext().unregisterReceiver( serviceRunningStateListner );
Logger.getInstance().unregisterEventObserver( this );
super.onPause();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onDestroy()
*/
@Override
protected void onDestroy()
{
// free field instances
prefManager.onDestroy();
setLogView( null );
super.onDestroy();
}
/*
* (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( eventHandler );
msg.sendToTarget();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onActivityResult(int, int,
* android.content.Intent)
*/
@Override
protected void
onActivityResult( int requestCode, int resultCode, Intent data )
{
// Make sure the request was successful
if ( resultCode == RESULT_OK )
{
switch ( requestCode )
{
case SELECT_EXT_RSA_PUBKEY_FILE:
{
String srcFile = data.getStringExtra( SDCFileBrowserActivity.FILE );
String destFile = getFilesDir().getPath() + File.separatorChar
+ Encryption.PUBLIC_KEY_FILE;
FileUtils.copy( srcFile, destFile );
break;
}
case SELECT_EXT_DEFAULTS_FILE:
{
String srcFile = data.getStringExtra( SDCFileBrowserActivity.FILE );
String destFile = getFilesDir().getPath() + File.separatorChar
+ getText( R.string.sdc_config_file_name ).toString();
if ( FileUtils.copy( srcFile, destFile ) )
{
prefManager.resetToDefaults( this.getApplicationContext() );
}
break;
}
}
}
}
}