/* * 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.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import de.unikassel.android.sdcframework.data.Sample; import de.unikassel.android.sdcframework.data.independent.SampleData; import de.unikassel.android.sdcframework.devices.facade.SensorDevice; import de.unikassel.android.sdcframework.provider.facade.ContentProviderData; import de.unikassel.android.sdcframework.util.Logger; import de.unikassel.android.sdcframework.util.TimeInformation; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Handler; /** * Abstract base class for content provider based device scanner types. <br/> * <br/> * A content provider based scanner does register itself as observer for a * content provider and creates samples from the notified data. It can be used * together with the {@link VirtualSensorDevice} to create virtual observable * sensor devices which are not related to a physical device. <br/> * <br/> * Just to mention it, this scanner is triggered by content provider changes and * is simply ignoring the configured device frequency. * * @see TwitterDeviceScanner * @author Katy Hilgenberg * */ public abstract class ContentProviderDeviceScanner extends AbstractSensorDeviceScanner { /** * The content resolver context */ private final ContentResolver resolver; /** * The URI for the scanned provider content */ private final Uri contentURI; /** * The content observer */ private final ContentObserver contentObserver; /** * Constructor * * @param resolver * the content resolver * @param contentURI */ public ContentProviderDeviceScanner( ContentResolver resolver, Uri contentURI ) { super(); this.resolver = resolver; if ( this.resolver == null ) throw new IllegalArgumentException( "resolver is null" ); this.contentURI = contentURI; this.contentObserver = new ContentObserver( new Handler() ) { /* * (non-Javadoc) * * @see android.database.ContentObserver#onChange(boolean) */ @Override public void onChange( boolean selfChange ) { super.onChange( selfChange ); doGatherSamples(); } }; } /* * (non-Javadoc) * * @see de.unikassel.android.sdcframework.devices.AbstractSensorDeviceScanner# * isCompatibleDevice * (de.unikassel.android.sdcframework.devices.facade.SensorDevice) */ @Override protected boolean isCompatibleDevice( SensorDevice device ) { return ( device instanceof VirtualSensorDevice ); } /** * Getter for the content resolver */ protected final ContentResolver getContentResolver() { return resolver; } /** * Getter for the content observer * * @return the content observer */ protected ContentObserver getContentObserver() { return contentObserver; } /** * Getter for the content URI * * @return the content URI */ protected final Uri getContentURI() { return contentURI; } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.facade.SensorDeviceScanner#start * (android.content.Context) */ @Override public final boolean start( Context context ) { getContentResolver().registerContentObserver( getContentURI(), true, contentObserver ); return true; } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.facade.SensorDeviceScanner#stop * (android.content.Context) */ @Override public final boolean stop( Context context ) { getContentResolver().unregisterContentObserver( contentObserver ); return true; } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.devices.AbstractSensorDeviceScanner#onDestroy * (android.content.Context) */ @Override public void onDestroy( Context context ) { super.onDestroy( context ); } /** * The gathering method to request available data from the content provider, * create samples from it, notify observers and finally remove gathered data * from the provider content. */ protected final synchronized void doGatherSamples() { // test for available data from the content provider ContentResolver contentResolver = getContentResolver(); if ( contentResolver != null ) { Queue< Long > rowIds = doNotifyForSamples( contentResolver ); // delete processed twitter data doDeleteGatheredData( contentResolver, rowIds ); } else { Logger.getInstance().error( this, "Content provider not available: " + getContentURI() ); } } /** * Method to delete content from the provider * * @param contentResolver * the content resolver to use * @param rowIds * the row id's to delete */ public void doDeleteGatheredData( ContentResolver contentResolver, Queue< Long > rowIds ) { try { // delete messages in a loop to avoid SQL statement limits exceeding while ( rowIds.size() > 0 ) { long count = Math.min( 200, rowIds.size() ); String[] sWhereArgs = new String[ rowIds.size() ]; StringBuffer sWhere = new StringBuffer( ContentProviderData._ID ); sWhere.append( " IN ( " ); int i = 0; for ( Long rowId : rowIds ) { if ( i > 0 ) { sWhere.append( ", " ); } sWhereArgs[ i ] = rowId.toString(); rowIds.remove( rowId ); sWhere.append( '?' ); ++i; if ( i >= count ) break; } sWhere.append( " )" ); contentResolver.delete( getContentURI(), sWhere.toString(), sWhereArgs ); } } catch ( Exception e ) { Logger.getInstance().error( this, "Exception while deleting samples from provider: " + e.getMessage() ); } } /** * Method to gather content from the provider and create sample notifications * * @param contentResolver * the content resolver to use * @return the id's of the gathered rows */ public final Queue< Long > doNotifyForSamples( ContentResolver contentResolver ) { Queue< Long > rowIds = new ConcurrentLinkedQueue< Long >(); Cursor cursor = contentResolver.query( getContentURI(), null, null, null, null ); try { if ( cursor.moveToFirst() ) { do { rowIds.add( cursor.getLong( cursor.getColumnIndexOrThrow( ContentProviderData._ID ) ) ); long timeStamp = cursor.getLong( cursor.getColumnIndex( ContentProviderData.TIMESTAMP ) ); boolean synced = cursor.getInt( cursor.getColumnIndex( ContentProviderData.SYNCED ) ) == 1; Sample sample = SampleFactory.getInstance().createSample( new TimeInformation( timeStamp, synced ), getDevice().getDeviceIdentifier(), getDevice().getConfiguration().getSamplePriority().ordinal(), getSampleDataFromCursor( cursor ) ); if ( sample != null ) { notify( sample ); } } while ( cursor.moveToNext() ); } } catch ( Exception e ) { Logger.getInstance().error( this, "Exception while reading content from provider: " + e.getMessage() ); } finally { cursor.close(); } return rowIds; } /** * Method to extract concrete sample data at database cursor position * * @param cursor * the database cursor * @return the extracted sample data */ protected abstract SampleData getSampleDataFromCursor( Cursor cursor ); }