/* * 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.devices; import java.util.concurrent.atomic.AtomicInteger; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; import android.widget.Toast; import de.unikassel.android.sdcframework.R; import de.unikassel.android.sdcframework.data.Sample; import de.unikassel.android.sdcframework.data.independent.LocationSampleData; import de.unikassel.android.sdcframework.data.independent.SampleData; import de.unikassel.android.sdcframework.devices.facade.SampleProvidingSensorDevice; import de.unikassel.android.sdcframework.devices.facade.SensorDeviceIdentifier; import de.unikassel.android.sdcframework.util.Logger; import de.unikassel.android.sdcframework.util.TimeInformation; import de.unikassel.android.sdcframework.util.TimeProvider; /** * Implementation of an abstract location provider based sensor device as sample * providing device. * * @see ScannerStateAwareSensorDevice * @see SampleProvidingSensorDevice * @author Katy Hilgenberg */ public abstract class AbstractLocationDevice extends ScannerStateAwareSensorDevice implements SampleProvidingSensorDevice { /** * The lower frequency to avoid battery drain */ private static final int LOWER_FREQUENCY = 30000; /** * Internal location listener implementation * * @author Katy Hilgenberg * */ private class ProviderLocationListener implements LocationListener { /** * The observed provider */ private final String provider; /** * The last known state */ private final AtomicInteger lastState; /** * Constructor * * @param provider * the observed provider */ public ProviderLocationListener( String provider ) { super(); lastState = new AtomicInteger( LocationProvider.AVAILABLE ); this.provider = provider; } /* * (non-Javadoc) * * @see * android.location.LocationListener#onLocationChanged(android.location. * Location) */ @Override public final void onLocationChanged( Location location ) { try { doHandleLocationChanged( location ); } catch ( Exception e ) { Logger.getInstance().error( this, "Exception in onLocationChanged" ); } } /* * (non-Javadoc) * * @see android.location.LocationListener#onStatusChanged(java.lang.String, * int, android.os.Bundle) */ @Override public final void onStatusChanged( String provider, int status, Bundle extras ) { try { if ( this.provider.equals( provider ) && status != lastState.getAndSet( status ) ) { if ( status == LocationProvider.OUT_OF_SERVICE ) { Logger.getInstance().warning( this, this.provider + " provider out of service" ); } } } catch ( Exception e ) { Logger.getInstance().error( this, "Exception in onStatusChanged" ); } } /* * (non-Javadoc) * * @see * android.location.LocationListener#onProviderEnabled(java.lang.String) */ @Override public final void onProviderEnabled( String provider ) { // nothing to do as this listener is only attached if GPS provider is // enabled } /* * (non-Javadoc) * * @see * android.location.LocationListener#onProviderDisabled(java.lang.String) */ @Override public final void onProviderDisabled( String provider ) { try { if ( this.provider.equals( provider ) ) { if ( isDeviceScanningEnabled() ) { doHandleDeviceDisabledBySystem( getContext() ); } } } catch ( Exception e ) { Logger.getInstance().error( this, "Exception in onProviderDisabled" ); } } } /** * Internal location provider state listener implementation * * @author Katy Hilgenberg * */ private class ProviderStateListener implements LocationListener { /** * The observed provider */ private final String provider; /** * Constructor * * @param provider * the provider to observe for state changes */ public ProviderStateListener( String provider ) { super(); this.provider = provider; } /* * (non-Javadoc) * * @see * android.location.LocationListener#onLocationChanged(android.location. * Location) */ @Override public final void onLocationChanged( Location location ) { // do nothing on location changes } /* * (non-Javadoc) * * @see android.location.LocationListener#onStatusChanged(java.lang.String, * int, android.os.Bundle) */ @Override public final void onStatusChanged( String provider, int status, Bundle extras ) { // nothing to do } /* * (non-Javadoc) * * @see * android.location.LocationListener#onProviderEnabled(java.lang.String) */ @Override public final void onProviderEnabled( String provider ) { try { if ( this.provider.equals( provider ) ) { if ( isDeviceScanningEnabled() ) { doHandleDeviceEnabledBySystem( getContext() ); } } } catch ( Exception e ) { Logger.getInstance().error( this, "Exception in onProviderEnabled" ); } } /* * (non-Javadoc) * * @see * android.location.LocationListener#onProviderDisabled(java.lang.String) */ @Override public final void onProviderDisabled( String provider ) { // nothing to do as this listener is only attached if either the GPS // provider is not enabled in the system or this device is already // disabled by configuration. } } /** * The service application context */ private Context context; /** * The GPS location listener used for sampling */ private ProviderLocationListener locationListener; /** * The GPS state listener used for adding/removal of location listener */ private ProviderStateListener stateListener; /** * The current available sample */ private final LocationSampleData currentSampleData; /** * Flag if location listener is registered */ private boolean isLocationListenerRegistered; /** * Flag if state listener is registered */ private boolean isStateListenerRegistered; /** * The location provider */ private final String provider; /** * Constructor * * @param context * the context * @param id * the sensor device identifier * @param provider * the network provider */ public AbstractLocationDevice( Context context, SensorDeviceIdentifier id, String provider ) { super( id ); this.provider = provider; setContext( context ); this.currentSampleData = new LocationSampleData(); } /** * Setter for the context * * @param context * the context to set */ private final void setContext( Context context ) { this.context = context; } /** * Getter for the context * * @return the context */ private final Context getContext() { return context; } /** * Getter for the locationManager * * @return the locationManager */ private final LocationManager getLocationManager() { return (LocationManager) getContext().getSystemService( Context.LOCATION_SERVICE ); } /** * Getter for the locationListener * * @return the locationListener */ private final ProviderLocationListener getLocationListener() { if ( locationListener == null ) { setLocationListener( new ProviderLocationListener( provider ) ); } return locationListener; } /** * Setter for the locationListener * * @param locationListener * the locationListener to set */ private final void setLocationListener( ProviderLocationListener locationListener ) { this.locationListener = locationListener; } /** * Setter for the stateListener * * @param stateListener * the stateListener to set */ private final void setStateListener( ProviderStateListener stateListener ) { this.stateListener = stateListener; } /** * Getter for the stateListener * * @return the stateListener */ private final ProviderStateListener getStateListener() { if ( stateListener == null ) { setStateListener( new ProviderStateListener( provider ) ); } return stateListener; } /** * Getter for the isLocationListenerRegistered flag * * @return the isLocationListenerRegistered flag */ public final boolean isLocationListenerRegistered() { return isLocationListenerRegistered; } /** * Setter for the isLocationListenerRegistered flag * * @param isLocationListenerRegistered * the isLocationListenerRegistered flag to set */ private final void setLocationListenerRegistered( boolean isLocationListenerRegistered ) { this.isLocationListenerRegistered = isLocationListenerRegistered; } /** * Getter for the isStateListenerRegistered flag * * @return the isStateListenerRegistered flag */ public boolean isStateListenerRegistered() { return isStateListenerRegistered; } /** * Setter for the isStateListenerRegistered flag * * @param isStateListenerRegistered * the isStateListenerRegistered flag to set */ public void setStateListenerRegistered( boolean isStateListenerRegistered ) { this.isStateListenerRegistered = isStateListenerRegistered; } /** * Does register the location listener */ private final void registerLocationListener() { if ( !isLocationListenerRegistered() ) { // limit frequency by internal minimum to avoid battery drain int frequency = Math.max( getConfiguration().getFrequency(), getLowerFrequency() ); LocationManager locationManager = getLocationManager(); getLocationListener().onLocationChanged( locationManager.getLastKnownLocation( provider ) ); locationManager.requestLocationUpdates( provider, frequency, getMinDistance(), getLocationListener() ); setLocationListenerRegistered( true ); Logger.getInstance().info( this, "location listener registered" ); } } /** * The minimum frequency for location updates * * @return the minimum frequency for location updates */ protected int getLowerFrequency() { return LOWER_FREQUENCY; } /** * The minimum distance in meters for location updates * * @return the minimum distance in meters */ protected float getMinDistance() { return 0; } /** * Does unregister the location listener */ private final void unregisterLocationListener() { if ( isLocationListenerRegistered() ) { getLocationManager().removeUpdates( getLocationListener() ); Logger.getInstance().info( this, "location listener unregistered" ); setLocationListenerRegistered( false ); } } /** * Does register the GPS state listener */ private final void registerStateListener() { if ( !isStateListenerRegistered() ) { // do set frequency and distance to high values as this listener is only // interested in state changes getLocationManager().requestLocationUpdates( provider, Long.MAX_VALUE, Float.MAX_VALUE, getStateListener() ); Logger.getInstance().info( this, provider + " state listener registered" ); setStateListenerRegistered( true ); } } /** * Does unregister the GPS state listener */ private final void unregisterStateListener() { if ( isStateListenerRegistered() ) { getLocationManager().removeUpdates( getStateListener() ); Logger.getInstance().info( this, provider + " state listener unregistered" ); setStateListenerRegistered( false ); } } /** * Handler for the location changed event * * @param location * the changed location */ public final synchronized void doHandleLocationChanged( Location location ) { if ( location != null ) { currentSampleData.setLatitude( location.getLatitude() ); currentSampleData.setLongitude( location.getLongitude() ); currentSampleData.setAltitude( location.hasAltitude() ? location.getAltitude() : null ); currentSampleData.setSpeed( location.hasSpeed() ? location.getSpeed() : null ); currentSampleData.setAccuracy( location.hasAccuracy() ? location.getAccuracy() : null ); } } /* * (non-Javadoc) * * @see de.unikassel.android.sdcframework.devices.facade.SensorDevice# * isDeviceInSystemEnabled(android.content.Context) */ @Override public final boolean isDeviceInSystemEnabled( Context context ) { return getLocationManager().isProviderEnabled( provider ); } /* * (non-Javadoc) * * @see de.unikassel.android.sdcframework.devices.AbstractSensorDevice# * doSignalDeviceNotEnabledInSystem(android.content.Context) */ @Override protected final void doSignalDeviceNotEnabledInSystem( Context applicationContext ) { registerStateListener(); String appName = applicationContext.getText( R.string.sdc_service_name ).toString(); int resID = getDeviceDisabledMessageID(); String message = applicationContext.getText( resID ).toString(); // for the moment we do just ask the user to enable device Toast.makeText( applicationContext, appName + ": " + message, Toast.LENGTH_LONG ).show(); Logger.getInstance().warning( this, message ); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.ScannerStateAwareSensorDevice * #onScannerRunningStateChange(boolean, android.content.Context) */ @Override protected final void onScannerRunningStateChange( boolean isRunning, Context context ) { if ( isRunning ) { unregisterStateListener(); registerLocationListener(); } else { unregisterLocationListener(); } } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.ScannerStateAwareSensorDevice * #onConfigurationChanged() */ @Override protected final void onConfigurationChanged() { super.onConfigurationChanged(); if ( isLocationListenerRegistered() ) { unregisterLocationListener(); registerLocationListener(); } } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.AbstractSensorDevice#onCreate * (android.content.Context) */ @Override public final void onCreate( Context context ) { super.onCreate( context ); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.AbstractSensorDevice#onDestroy * (android.content.Context) */ @Override public final void onDestroy( Context context ) { unregisterStateListener(); super.onDestroy( context ); setLocationListener( null ); setStateListener( null ); setContext( null ); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.AbstractSensorDevice#isAirplaneModeOn * (android.content.Context) */ @Override public final boolean isAirplaneModeOn( Context applicationContext ) { // GPS should work even in airplane mode return false; } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.facade.SampleProvidingSensorDevice * #hasSample() */ @Override public boolean hasSample() { return currentSampleData.getLatitude() != null && currentSampleData.getLongitude() != null; } /** * Getter for the currentSampleData * * @return the currentSampleData */ protected LocationSampleData getLocationData() { return currentSampleData; } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.facade.SampleProvidingSensorDevice * #getSample() */ @Override public final synchronized Sample getSample() { TimeInformation ti = TimeProvider.getInstance().getAccurateTimeInformation(); Sample sample = SampleFactory.getInstance().createSample( ti, getDeviceIdentifier(), getConfiguration().getSamplePriority().ordinal(), getCurrentSampleData() ); return sample; } /** * Method to get the current sample data * * @return the current sample data */ protected abstract SampleData getCurrentSampleData(); /** * Method to get the device disabled message * * @return the device disabled message */ protected abstract int getDeviceDisabledMessageID(); }