/** * Funf: Open Sensing Framework * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. * Acknowledgments: Alan Gardner * Contact: nadav@media.mit.edu * * This file is part of Funf. * * Funf 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. * * Funf 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 Funf. If not, see <http://www.gnu.org/licenses/>. */ package edu.mit.media.funf.probe; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; import java.util.concurrent.Executor; import android.content.Context; import android.database.Cursor; import android.database.CursorIndexOutOfBoundsException; import android.os.Bundle; import android.provider.MediaStore.Images; import android.util.Log; import edu.mit.media.funf.Utils; import edu.mit.media.funf.probe.CursorCell.AnyCell; import edu.mit.media.funf.probe.CursorCell.BooleanCell; import edu.mit.media.funf.probe.CursorCell.DoubleCell; import edu.mit.media.funf.probe.CursorCell.HashedCell; import edu.mit.media.funf.probe.CursorCell.IntCell; import edu.mit.media.funf.probe.CursorCell.LongCell; import edu.mit.media.funf.probe.CursorCell.StringCell; public abstract class ContentProviderProbe extends Probe { protected Iterable<Bundle> mostRecentScan; private Thread onRunThread; private ThreadPerTaskExecutor onRunExecutor; @Override public Parameter[] getAvailableParameters() { return new Parameter[] { new Parameter(Parameter.Builtin.PERIOD, getDefaultPeriod()), new Parameter(Parameter.Builtin.START, 0L), new Parameter(Parameter.Builtin.END, 0L) }; } protected long getDefaultPeriod() { return 3600L; } @Override public String[] getRequiredFeatures() { return null; } @Override protected void onEnable() { // Nothing } @Override protected void onDisable() { // Nothing } @Override protected void onRun(Bundle params) { Log.d(TAG, "onRun of ContentProviderProbe"); /* if (onRunThread == null) { onRunThread = new Thread(new Runnable() { @Override public void run() { mostRecentScan = parseCursorResults(); sendProbeData(); onRunThread = null; stop(); } }); onRunThread.start(); }*/ if (onRunExecutor == null) { onRunExecutor = new ThreadPerTaskExecutor(); onRunExecutor.execute(new Runnable() { @Override public void run() { mostRecentScan = parseCursorResults(); sendProbeData(); onRunThread = null; stop(); } }); } } @Override protected void onStop() { if (onRunThread != null) { try { onRunThread.join(4000); } catch (InterruptedException e) { Log.e(TAG, "Didn't finish sending before probe was stopped"); } onRunThread = null; } disable(); } private static final long THROTTLE_SLEEP_MILLIS = 10; private void throttle() { try { Thread.sleep(THROTTLE_SLEEP_MILLIS); } catch (InterruptedException e) { Log.e(TAG, "Throttled thread interrupted.", e); } } @Override public void sendProbeData() { int counter = 0; // TODO: make this always run on separate thread if (mostRecentScan != null ) { if (sendEachRowSeparately()) { for (Bundle data : mostRecentScan) { if (data != null) { Log.e(TAG, "Adding item : " + counter + " from " + getTimestamp(data) + "@" + System.currentTimeMillis()); sendProbeData(getTimestamp(data), data); throttle(); counter ++; } } } else { ArrayList<Bundle> results = new ArrayList<Bundle>(); long timestamp = 0L; for (Bundle item : mostRecentScan) { if (item != null) { if (timestamp == 0L) { timestamp = getTimestamp(item); // use first item for timestamp } results.add(item); throttle(); } if (results.size() >= 100) { Bundle data = new Bundle(); data.putParcelableArrayList(getDataName(), results); sendProbeData(timestamp, data); results = new ArrayList<Bundle>(); } } Bundle data = new Bundle(); data.putParcelableArrayList(getDataName(), results); if (timestamp == 0L) { timestamp = Utils.getTimestamp(); // use current time if no results } sendProbeData(timestamp, data); } } } protected boolean sendEachRowSeparately() { return true; } protected abstract String getDataName(); protected abstract long getTimestamp(Bundle result); protected abstract long getTimestamp(List<Bundle> results); protected abstract Map<String,CursorCell<?>> getProjectionMap(); protected abstract Cursor getCursor(String[] projection); protected Bundle parseDataBundle(Cursor cursor, String[] projection, Map<String,CursorCell<?>> projectionMap) { Bundle b = new Bundle(); for (String key : projection) { CursorCell<?> cursorCell = projectionMap.get(key); if (cursorCell != null) { Object value = cursorCell.getData(cursor, key); if (value != null) { Utils.putInBundle(b, key,value); } } } return b; } private Iterable<Bundle> parseCursorResults() { return new Iterable<Bundle>() { @Override public Iterator<Bundle> iterator() { return new ContentProviderIterator(); } }; } class ThreadPerTaskExecutor implements Executor { final Queue<Runnable> tasks = new ArrayDeque<Runnable>(); final Executor executor; Runnable active; ThreadPerTaskExecutor(Executor executor) { this.executor = executor; } ThreadPerTaskExecutor() { this.executor = new SmallExecutor(); } public synchronized void execute(final Runnable r) { tasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (active == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((active = tasks.poll()) != null) { executor.execute(active); } } class SmallExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); }} } class ContentProviderIterator implements Iterator<Bundle> { private final Cursor c; private final String[] projection; private final Map<String, CursorCell<?>> projectionMap; private boolean brandNew; // Next has not been called public ContentProviderIterator() { this.projectionMap = getProjectionMap(); this.projection = new String[projectionMap.size()]; projectionMap.keySet().toArray(projection); this.c = getCursor(projection); int count = c.getCount(); this.brandNew = true; Log.v(TAG, "cursor returned " + count +" result"); } @Override public boolean hasNext() { //Log.d(TAG, "Checking has next"); boolean hasNext = brandNew ? c.moveToFirst() : !(c.isClosed() || c.isLast() || c.isAfterLast()); if (!hasNext && !c.isClosed()) c.close(); return hasNext; } @Override public Bundle next() { if (brandNew) { c.moveToFirst(); brandNew = false; } else { c.moveToNext(); } Bundle dataBundle = null; try { dataBundle = parseDataBundle(c, projection, projectionMap); } catch (CursorIndexOutOfBoundsException e) { throw new NoSuchElementException(); } finally { hasNext(); // Called to ensure the cursor is closed } return dataBundle; } @Override public void remove() { throw new UnsupportedOperationException(); } } // Convenience methods that can be used to cache and reuse CursorCell objects protected static BooleanCell booleanCell() { return new BooleanCell(); } protected static IntCell intCell() { return new IntCell(); } protected static LongCell longCell() { return new LongCell(); } protected static DoubleCell doubleCell() { return new DoubleCell(); } protected static StringCell stringCell() { return new StringCell(); } protected static AnyCell anyCell() { return new AnyCell(); } protected CursorCell<String> hashedStringCell() { return hashedStringCell(this); } protected static CursorCell<String> hashedStringCell(Context context) { return new HashedCell(context, stringCell()); } }