package org.ohmage.probemanager; import android.app.Service; import android.content.ContentValues; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; import org.ohmage.AccountHelper; import org.ohmage.UserPreferencesHelper; import org.ohmage.probemanager.DbContract.Probes; import org.ohmage.probemanager.DbContract.Responses; import java.lang.ref.WeakReference; import java.util.ArrayList; public class ProbeManager extends Service { /** * Set to true if we want to buffer points */ private static final boolean BUFFER_POINTS = true; /** * buffer 25% of available memory, up to a maximum of 16MB */ private static final long MAX_BYTE_SIZE = Math.min(Runtime.getRuntime().maxMemory() / 4, 16 * 1024 * 1024); /** * Maximum number of points which should be in the buffer at any give time */ private static final int MAX_BUFFER = 600; /** * Maximum number of milliseconds to wait before flushing data to db */ private static final long FLUSH_DELAY = 500; /** * Number of bytes allocated to the data portions of probePoints */ private int probeByteSize = 0; /** * Number of bytes allocated to the data portion of responsePoints */ private int responseByteSize = 0; /** * Probe data */ ArrayList<ContentValues> probePoints = new ArrayList<ContentValues>(); /** * Response data */ ArrayList<ContentValues> responsePoints = new ArrayList<ContentValues>(); /** * Handles a flush message * * @author cketcham */ static class PointFlushHandler extends Handler { private final WeakReference<ProbeManager> mService; PointFlushHandler(ProbeManager service) { mService = new WeakReference<ProbeManager>(service); } @Override public void handleMessage(Message msg) { ProbeManager service = mService.get(); if (service != null) { service.flushProbes(); service.flushResponses(); } } } PointFlushHandler mHandler = new PointFlushHandler(this); private AccountHelper mAccount; @Override public IBinder onBind(Intent intent) { return new IProbeManager.Stub() { @Override public boolean writeProbe(String observerId, int observerVersion, String streamId, int streamVersion, int uploadPriority, String metadata, String data) throws RemoteException { // Don't write a probe unless a user is logged into ohmage if (TextUtils.isEmpty(mAccount.getUsername())) { return false; } ContentValues values = new ContentValues(); values.put(Probes.OBSERVER_ID, observerId); values.put(Probes.OBSERVER_VERSION, observerVersion); values.put(Probes.STREAM_ID, streamId); values.put(Probes.STREAM_VERSION, streamVersion); values.put(Probes.UPLOAD_PRIORITY, uploadPriority); values.put(Probes.PROBE_METADATA, metadata); values.put(Probes.PROBE_DATA, data); values.put(Probes.USERNAME, mAccount.getUsername()); if (BUFFER_POINTS) { synchronized (probePoints) { if(data != null) probeByteSize += data.length(); if(metadata != null) probeByteSize += metadata.length(); probePoints.add(values); if (probePoints.size() > MAX_BUFFER || probeByteSize + responseByteSize > MAX_BYTE_SIZE) { mHandler.removeMessages(0); flushProbes(); } else queueFlush(); } return true; } else { return getContentResolver().insert(Probes.CONTENT_URI, values) != null; } } @Override public boolean writeResponse(String campaignUrn, String campaignCreationTimestamp, int uploadPriority, String data) throws RemoteException { // Don't write a response unless a user is logged into ohmage if (TextUtils.isEmpty(mAccount.getUsername())) return false; ContentValues values = new ContentValues(); values.put(Responses.CAMPAIGN_URN, campaignUrn); values.put(Responses.CAMPAIGN_CREATED, campaignCreationTimestamp); values.put(Responses.UPLOAD_PRIORITY, uploadPriority); values.put(Responses.RESPONSE_DATA, data); values.put(Responses.USERNAME, mAccount.getUsername()); if (BUFFER_POINTS) { synchronized (responsePoints) { if(data != null) responseByteSize += data.length(); responsePoints.add(values); if (responsePoints.size() > MAX_BUFFER || probeByteSize + responseByteSize > MAX_BYTE_SIZE) { mHandler.removeMessages(0); flushResponses(); } else queueFlush(); } return true; } else { return getContentResolver().insert(Responses.CONTENT_URI, values) != null; } } }; } @Override public void onCreate() { super.onCreate(); mAccount = new AccountHelper(this); } @Override public void onLowMemory() { super.onLowMemory(); flushProbes(); flushResponses(); } @Override public void onDestroy() { super.onDestroy(); flushProbes(); flushResponses(); } private void flushProbes() { ContentValues[] toFlush; synchronized (probePoints) { toFlush = probePoints.toArray(new ContentValues[] {}); probePoints.clear(); probeByteSize = 0; } getContentResolver().bulkInsert(Probes.CONTENT_URI, toFlush); } private void flushResponses() { ContentValues[] toFlush; synchronized (responsePoints) { toFlush = responsePoints.toArray(new ContentValues[] {}); responsePoints.clear(); responseByteSize = 0; } getContentResolver().bulkInsert(Responses.CONTENT_URI, toFlush); } private void queueFlush() { mHandler.removeMessages(0); mHandler.sendEmptyMessageDelayed(0, FLUSH_DELAY); } }