implement location aggregator
Change-Id: I48a02766741474fa33a3f56e60db12db114a032d
diff --git a/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/HistogramPredictor.java b/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/HistogramPredictor.java
new file mode 100644
index 0000000..d79afc5
--- /dev/null
+++ b/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/HistogramPredictor.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.bordeaux.learning;
+
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+
+/**
+ * A histogram based predictor which records co-occurrences of applations with a speficic feature,
+ * for example, location, * time of day, etc. The histogram is kept in a two level hash table.
+ * The first level key is the feature value and the second level key is the app id.
+ */
+
+// TODO: Use Parceable or Serializable to load and save this class
+public class HistogramPredictor {
+ final static String TAG = "HistogramPredictor";
+
+ private HashMap<String, HistogramCounter> mPredictor =
+ new HashMap<String, HistogramCounter>();
+
+ private static final double FEATURE_INACTIVE_LIKELIHOOD = 0.00000001;
+ private final double logInactive = Math.log(FEATURE_INACTIVE_LIKELIHOOD);
+
+ /*
+ * This class keeps the histogram counts for each feature and provide the
+ * joint probabilities of <feature, class>.
+ */
+ private class HistogramCounter {
+ private HashMap<String, HashMap<String, Integer> > mCounter =
+ new HashMap<String, HashMap<String, Integer> >();
+ private int mTotalCount;
+
+ public HistogramCounter() {
+ resetCounter();
+ }
+
+ public void setCounter(HashMap<String, HashMap<String, Integer> > counter) {
+ resetCounter();
+ mCounter.putAll(counter);
+
+ // get total count
+ for (Map.Entry<String, HashMap<String, Integer> > entry : counter.entrySet()) {
+ for (Integer value : entry.getValue().values()) {
+ mTotalCount += value.intValue();
+ }
+ }
+ }
+
+ public void resetCounter() {
+ mCounter.clear();
+ mTotalCount = 0;
+ }
+
+ public void addSample(String className, String featureValue) {
+ HashMap<String, Integer> classCounts;
+
+ if (!mCounter.containsKey(featureValue)) {
+ classCounts = new HashMap<String, Integer>();
+ mCounter.put(featureValue, classCounts);
+ }
+ classCounts = mCounter.get(featureValue);
+
+ int count = (classCounts.containsKey(className)) ?
+ classCounts.get(className) + 1 : 1;
+ classCounts.put(className, count);
+ mTotalCount++;
+ }
+
+ public HashMap<String, Double> getClassScores(String featureValue) {
+ HashMap<String, Double> classScores = new HashMap<String, Double>();
+
+ double logTotalCount = Math.log((double) mTotalCount);
+ if (mCounter.containsKey(featureValue)) {
+ for(Map.Entry<String, Integer> entry :
+ mCounter.get(featureValue).entrySet()) {
+ double score =
+ Math.log((double) entry.getValue()) - logTotalCount;
+ classScores.put(entry.getKey(), score);
+ }
+ }
+ return classScores;
+ }
+
+ public HashMap<String, HashMap<String, Integer> > getCounter() {
+ return mCounter;
+ }
+ }
+
+ /*
+ * Given a map of feature name -value pairs returns the mostly likely apps to
+ * be launched with corresponding likelihoods.
+ */
+ public List<Map.Entry<String, Double> > findTopClasses(Map<String, String> features, int topK) {
+ // Most sophisticated function in this class
+ HashMap<String, Double> appScores = new HashMap<String, Double>();
+ double defaultLikelihood = mPredictor.size() * logInactive;
+
+ // compute all app scores
+ for (Map.Entry<String, HistogramCounter> entry : mPredictor.entrySet()) {
+ String featureName = entry.getKey();
+ HistogramCounter counter = entry.getValue();
+
+ if (features.containsKey(featureName)) {
+ String featureValue = features.get(featureName);
+ HashMap<String, Double> scoreMap = counter.getClassScores(featureValue);
+
+ for (Map.Entry<String, Double> item : scoreMap.entrySet()) {
+ String appName = item.getKey();
+ double appScore = item.getValue();
+
+ double score = (appScores.containsKey(appName)) ?
+ appScores.get(appName) : defaultLikelihood;
+ score += appScore - logInactive;
+
+ appScores.put(appName, score);
+ }
+ }
+ }
+
+ // sort app scores
+ List<Map.Entry<String, Double> > appList =
+ new ArrayList<Map.Entry<String, Double> >(appScores.size());
+ appList.addAll(appScores.entrySet());
+ Collections.sort(appList, new Comparator<Map.Entry<String, Double> >() {
+ public int compare(Map.Entry<String, Double> o1,
+ Map.Entry<String, Double> o2) {
+ return o2.getValue().compareTo(o1.getValue());
+ }
+ });
+
+ Log.e(TAG, "findTopApps appList: " + appList);
+ return appList;
+ }
+
+ /*
+ * Add a new observation of given sample id and features to the histograms
+ */
+ public void addSample(String sampleId, Map<String, String> features) {
+ for (Map.Entry<String, HistogramCounter> entry : mPredictor.entrySet()) {
+ String featureName = entry.getKey();
+ HistogramCounter counter = entry.getValue();
+
+ if (features.containsKey(featureName)) {
+ String featureValue = features.get(featureName);
+ counter.addSample(sampleId, featureValue);
+ }
+ }
+ }
+
+ /*
+ * reset predictor to a empty model
+ */
+ public void resetPredictor() {
+ // TODO: not sure this step would reduce memory waste
+ for (HistogramCounter counter : mPredictor.values()) {
+ counter.resetCounter();
+ }
+ mPredictor.clear();
+ }
+
+ /*
+ * specify a feature to used for prediction
+ */
+ public void useFeature(String featureName) {
+ if (!mPredictor.containsKey(featureName)) {
+ mPredictor.put(featureName, new HistogramCounter());
+ }
+ }
+
+ /*
+ * convert the prediction model into a byte array
+ */
+ public byte[] getModel() {
+ // TODO: convert model to a more memory efficient data structure.
+ HashMap<String, HashMap<String, HashMap<String, Integer > > > model =
+ new HashMap<String, HashMap<String, HashMap<String, Integer > > >();
+ for(Map.Entry<String, HistogramCounter> entry : mPredictor.entrySet()) {
+ model.put(entry.getKey(), entry.getValue().getCounter());
+ }
+
+ try {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ ObjectOutputStream objStream = new ObjectOutputStream(byteStream);
+ objStream.writeObject(model);
+ byte[] bytes = byteStream.toByteArray();
+ //Log.i(TAG, "getModel: " + bytes);
+ return bytes;
+ } catch (IOException e) {
+ throw new RuntimeException("Can't get model");
+ }
+ }
+
+ /*
+ * set the prediction model from a model data in the format of byte array
+ */
+ public boolean setModel(final byte[] modelData) {
+ HashMap<String, HashMap<String, HashMap<String, Integer > > > model;
+
+ try {
+ ByteArrayInputStream input = new ByteArrayInputStream(modelData);
+ ObjectInputStream objStream = new ObjectInputStream(input);
+ model = (HashMap<String, HashMap<String, HashMap<String, Integer > > >)
+ objStream.readObject();
+ } catch (IOException e) {
+ throw new RuntimeException("Can't load model");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Learning class not found");
+ }
+
+ resetPredictor();
+ for (Map.Entry<String, HashMap<String, HashMap<String, Integer> > > entry :
+ model.entrySet()) {
+ useFeature(entry.getKey());
+ mPredictor.get(entry.getKey()).setCounter(entry.getValue());
+ }
+ return true;
+ }
+}
diff --git a/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/predictorHist.java b/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/PredictorHist.java
similarity index 90%
rename from bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/predictorHist.java
rename to bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/PredictorHist.java
index 7924424..c332be5 100644
--- a/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/predictorHist.java
+++ b/bordeaux/learning/predictor_histogram/java/android/bordeaux/learning/PredictorHist.java
@@ -25,18 +25,18 @@
* A simple impelentation of histograms with sparse enteries using HashMap.
* User can push examples or extract probabilites from this histogram.
*/
-public class predictorHist {
+public class PredictorHist {
private HashMap<String, Integer> mCountHist;
private int mSampleCount;
String TAG = "PredicrtHist";
- public predictorHist() {
+ public PredictorHist() {
mCountHist = new HashMap<String, Integer>();
mSampleCount = 0;
}
// reset histogram
- public void ResetPredictorHist() {
+ public void resetPredictorHist() {
mCountHist.clear();
mSampleCount = 0;
}
@@ -52,7 +52,7 @@
//setter
public void set(HashMap<String, Integer> hist) {
- ResetPredictorHist();
+ resetPredictorHist();
for (Map.Entry<String, Integer> x : hist.entrySet()) {
mCountHist.put(x.getKey(), x.getValue());
mSampleCount = mSampleCount + x.getValue();
@@ -64,8 +64,9 @@
*/
public void pushSample( String fs) {
int histValue = 1;
- if (mCountHist.get(fs) != null )
+ if (mCountHist.containsKey(fs)) {
histValue = histValue + mCountHist.get(fs);
+ }
mCountHist.put(fs,histValue);
mSampleCount++;
}
@@ -75,8 +76,9 @@
*/
public float getProbability(String fs) {
float res = 0;
- if (mCountHist.get(fs) != null )
+ if (mCountHist.containsKey(fs)) {
res = ((float) mCountHist.get(fs)) / ((float)mSampleCount);
+ }
return res;
}
}
diff --git a/bordeaux/service/AndroidManifest.xml b/bordeaux/service/AndroidManifest.xml
index 0359fc6..5e5ec3f 100644
--- a/bordeaux/service/AndroidManifest.xml
+++ b/bordeaux/service/AndroidManifest.xml
@@ -4,6 +4,7 @@
android:versionName="1.0" package="android.bordeaux">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application android:label="@string/bordeaux_app"
android:debuggable="true"
android:hardwareAccelerated="true">
diff --git a/bordeaux/service/src/android/bordeaux/services/BaseCluster.java b/bordeaux/service/src/android/bordeaux/services/BaseCluster.java
new file mode 100644
index 0000000..1d595f7
--- /dev/null
+++ b/bordeaux/service/src/android/bordeaux/services/BaseCluster.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bordeaux.services;
+
+import android.location.Location;
+import android.text.format.Time;
+import android.util.Log;
+
+import java.lang.Math;
+import java.util.ArrayList;
+
+public class BaseCluster {
+
+ public static String TAG = "BaseCluster";
+
+ protected double[] mCenter;
+
+ protected long mAvgInterval;
+ protected long mDuration;
+
+ protected static final double EARTH_RADIUS = 6378100f;
+
+ public BaseCluster() {
+ }
+
+ public BaseCluster(Location location, long avgInterval) {
+ mAvgInterval = avgInterval;
+ mCenter = getLocationVector(location);
+
+ mDuration = 0;
+ }
+
+ protected double[] getLocationVector(Location location) {
+ double vector[] = new double[3];
+ double lambda = Math.toRadians(location.getLongitude());
+ double phi = Math.toRadians(location.getLatitude());
+ vector[0] = Math.cos(lambda) * Math.cos(phi);
+ vector[1] = Math.sin(lambda) * Math.cos(phi);
+ vector[2] = Math.sin(phi);
+ return vector;
+ }
+
+ private double computeDistance(double[] vector1, double[] vector2) {
+ double product = 0f;
+ for (int i = 0; i < 3; ++i) {
+ product += vector1[i] * vector2[i];
+ }
+ double radian = Math.acos(Math.min(product, 1f));
+ return radian * EARTH_RADIUS;
+ }
+
+ /*
+ * This computes the distance from loation to the cluster center in meter.
+ */
+ public float distanceToCenter(Location location) {
+ return (float) computeDistance(mCenter, getLocationVector(location));
+ }
+
+ public float distanceToCluster(BaseCluster cluster) {
+ return (float) computeDistance(mCenter, cluster.mCenter);
+ }
+
+ public void absorbCluster(BaseCluster cluster) {
+ if (cluster.mAvgInterval != mAvgInterval) {
+ throw new RuntimeException(
+ "aborbing cluster failed: inconsistent average invergal ");
+ }
+
+ double currWeight = ((double) mDuration) / (mDuration + cluster.mDuration);
+ double newWeight = 1f - currWeight;
+ double norm = 0;
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] = currWeight * mCenter[i] + newWeight * cluster.mCenter[i];
+ norm += mCenter[i] * mCenter[i];
+ }
+ // normalize
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] /= norm;
+ }
+ mDuration += cluster.mDuration;
+ }
+
+ public boolean passThreshold(long durationThreshold) {
+ // TODO: might want to keep semantic cluster
+ return mDuration > durationThreshold;
+ }
+}
diff --git a/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java b/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java
index a26e9cd..7e2346f 100644
--- a/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java
+++ b/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java
@@ -35,11 +35,12 @@
private IAggregatorManager mAggregatorManager;
public boolean retrieveAggregatorManager() {
- if (mAggregatorManager == null)
- mAggregatorManager = BordeauxManagerService.getAggregatorManager(mContext);
if (mAggregatorManager == null) {
- Log.e(TAG, AggregatorManager_NOTAVAILABLE);
- return false;
+ mAggregatorManager = BordeauxManagerService.getAggregatorManager(mContext);
+ if (mAggregatorManager == null) {
+ Log.e(TAG, AggregatorManager_NOTAVAILABLE);
+ return false;
+ }
}
return true;
}
@@ -61,9 +62,10 @@
}
private Map<String, String> getMap(final List<StringString> sample) {
- HashMap<String, String> m = new HashMap<String, String>();
- for (int i =0; i < sample.size(); i++)
- m.put(sample.get(i).key, sample.get(i).value);
- return (Map) m;
+ HashMap<String, String> map = new HashMap<String, String>();
+ for (int i =0; i < sample.size(); i++) {
+ map.put(sample.get(i).key, sample.get(i).value);
+ }
+ return (Map) map;
}
}
diff --git a/bordeaux/service/src/android/bordeaux/services/BordeauxManagerService.java b/bordeaux/service/src/android/bordeaux/services/BordeauxManagerService.java
index 65ffdda..00fbb30 100644
--- a/bordeaux/service/src/android/bordeaux/services/BordeauxManagerService.java
+++ b/bordeaux/service/src/android/bordeaux/services/BordeauxManagerService.java
@@ -57,7 +57,6 @@
context.bindService(new Intent(IBordeauxService.class.getName()),
mConnection, Context.BIND_AUTO_CREATE);
mStarted = true;
-
}
// Call the release, before the Context gets destroyed.
diff --git a/bordeaux/service/src/android/bordeaux/services/BordeauxPredictor.java b/bordeaux/service/src/android/bordeaux/services/BordeauxPredictor.java
index cd0e57e..54d96a2 100644
--- a/bordeaux/service/src/android/bordeaux/services/BordeauxPredictor.java
+++ b/bordeaux/service/src/android/bordeaux/services/BordeauxPredictor.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
@@ -35,16 +36,6 @@
private String mName;
private IPredictor mPredictor;
- public boolean retrievePredictor() {
- if (mPredictor == null)
- mPredictor = BordeauxManagerService.getPredictor(mContext, mName);
- if (mPredictor == null) {
- Log.e(TAG, PREDICTOR_NOTAVAILABLE);
- return false;
- }
- return true;
- }
-
public BordeauxPredictor(Context context) {
mContext = context;
mName = "defaultPredictor";
@@ -63,31 +54,54 @@
return false;
}
try {
- mPredictor.ResetPredictor();
+ mPredictor.resetPredictor();
return true;
} catch (RemoteException e) {
}
return false;
}
- public void pushSample(String s) {
+ public boolean retrievePredictor() {
+ if (mPredictor == null) {
+ mPredictor = BordeauxManagerService.getPredictor(mContext, mName);
+ }
+ if (mPredictor == null) {
+ Log.e(TAG, PREDICTOR_NOTAVAILABLE);
+ return false;
+ }
+ return true;
+ }
+
+ public void addSample(String sampleName) {
if (!retrievePredictor())
throw new RuntimeException(PREDICTOR_NOTAVAILABLE);
try {
- mPredictor.pushNewSample(s);
+ mPredictor.pushNewSample(sampleName);
} catch (RemoteException e) {
Log.e(TAG,"Exception: pushing a new example");
throw new RuntimeException(PREDICTOR_NOTAVAILABLE);
}
}
- public float getProbability(String s) {
- if (!retrievePredictor())
- throw new RuntimeException(PREDICTOR_NOTAVAILABLE);
+ public ArrayList<Pair<String, Float> > getTopSamples() {
+ return getTopSamples(0);
+ }
+
+ public ArrayList<Pair<String, Float> > getTopSamples(int topK) {
try {
- return mPredictor.getSampleProbability(s);
- } catch (RemoteException e) {
- Log.e(TAG,"Exception: getting sample probability");
+ ArrayList<StringFloat> topList =
+ (ArrayList<StringFloat>) mPredictor.getTopCandidates(topK);
+
+ ArrayList<Pair<String, Float> > topSamples =
+ new ArrayList<Pair<String, Float> >(topList.size());
+ for (int i = 0; i < topList.size(); ++i) {
+ topSamples.add(new Pair<String, Float>(topList.get(i).key, topList.get(i).value));
+ }
+ Log.e(TAG, "getTopSamples: " + topSamples);
+
+ return topSamples;
+ } catch(RemoteException e) {
+ Log.e(TAG,"Exception: getTopSamples");
throw new RuntimeException(PREDICTOR_NOTAVAILABLE);
}
}
diff --git a/bordeaux/service/src/android/bordeaux/services/BordeauxService.java b/bordeaux/service/src/android/bordeaux/services/BordeauxService.java
index 16bb78f..ab6f0f1 100644
--- a/bordeaux/service/src/android/bordeaux/services/BordeauxService.java
+++ b/bordeaux/service/src/android/bordeaux/services/BordeauxService.java
@@ -75,7 +75,7 @@
//mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
mSessionManager = new BordeauxSessionManager(this);
mMotionStatsAggregator = new MotionStatsAggregator();
- mLocationStatsAggregator = new LocationStatsAggregator();
+ mLocationStatsAggregator = new LocationStatsAggregator(this);
mTimeStatsAggregator = new TimeStatsAggregator();
mAggregatorManager = AggregatorManager.getInstance();
mAggregatorManager.registerAggregator(mMotionStatsAggregator, mAggregatorManager);
diff --git a/bordeaux/service/src/android/bordeaux/services/ClusterManager.java b/bordeaux/service/src/android/bordeaux/services/ClusterManager.java
new file mode 100644
index 0000000..88ba1f3
--- /dev/null
+++ b/bordeaux/service/src/android/bordeaux/services/ClusterManager.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bordeaux.services;
+
+import android.location.Location;
+import android.text.format.Time;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * ClusterManager incrementally indentify representitve clusters from the input location
+ * stream. Clusters are updated online using leader based clustering algorithm. The input
+ * locations initially are kept by the clusters. Periodially, a cluster consolidating
+ * procedure is carried out to refine the cluster centers. After consolidation, the
+ * location data are released.
+ */
+public class ClusterManager {
+
+ private static String TAG = "ClusterManager";
+
+ private static float LOCATION_CLUSTER_RADIUS = 25; // meter
+
+ private static float SEMANTIC_CLUSTER_RADIUS = 50; // meter
+
+ private static long CONSOLIDATE_INTERVAL = 90000; // is milliseconds
+
+ private static long LOCATION_CLUSTER_THRESHOLD = 1000; // in milliseconds
+
+ private static long SEMANTIC_CLUSTER_THRESHOLD = 30000; // in milliseconds
+
+ private Location mLastLocation = null;
+
+ private long mTimeRef = 0;
+
+ private long mSemanticClusterCount = 0;
+
+ private ArrayList<LocationCluster> mLocClusters = new ArrayList<LocationCluster>();
+
+ private ArrayList<SemanticCluster> mSemanticClusters = new ArrayList<SemanticCluster>();
+
+ public ClusterManager() {
+ }
+
+ public void addSample(Location location) {
+ float bestClusterDistance = Float.MAX_VALUE;
+ int bestClusterIndex = -1;
+ long lastDuration;
+ long currentTime = location.getTime();
+
+ if (mLastLocation != null) {
+ // get the duration spent in the last location
+ long duration = location.getTime() - mLastLocation.getTime();
+ // TODO: set duration is a separate field
+ mLastLocation.setTime(duration);
+ Log.v(TAG, "sample duration: " + duration +
+ ", number of clusters: " + mLocClusters.size());
+
+ // add the last location to cluster.
+ // first find the cluster it belongs to.
+ for (int i = 0; i < mLocClusters.size(); ++i) {
+ float distance = mLocClusters.get(i).distanceToCenter(mLastLocation);
+ Log.v(TAG, "clulster " + i + " is within " + distance + " meters");
+ if (distance < bestClusterDistance) {
+ bestClusterDistance = distance;
+ bestClusterIndex = i;
+ }
+ }
+
+ // add the location to the selected cluster
+ if (bestClusterDistance < LOCATION_CLUSTER_RADIUS) {
+ Log.v(TAG, "add sample to cluster: " + bestClusterIndex + ",( " +
+ location.getLongitude() + ", " + location.getLatitude() + ")");
+ mLocClusters.get(bestClusterIndex).addSample(mLastLocation);
+ } else {
+ // if it is far away from all existing clusters, create a new cluster.
+ LocationCluster cluster =
+ new LocationCluster(mLastLocation, CONSOLIDATE_INTERVAL);
+ // move the center of the new cluster if its covering region overlaps
+ // with an existing cluster.
+ if (bestClusterDistance < 2 * LOCATION_CLUSTER_RADIUS) {
+ cluster.moveAwayCluster(mLocClusters.get(bestClusterIndex),
+ ((float) 2 * LOCATION_CLUSTER_RADIUS));
+ }
+ mLocClusters.add(cluster);
+ }
+ } else {
+ mTimeRef = currentTime;
+ }
+
+ long collectDuration = currentTime - mTimeRef;
+ Log.e(TAG, "collect duration: " + collectDuration);
+ if (collectDuration > CONSOLIDATE_INTERVAL) {
+ // TODO : conslidation takes time. move this to a separate thread later.
+ consolidateClusters(collectDuration);
+ mTimeRef = currentTime;
+ }
+
+ /*
+ // TODO: this could be removed
+ Log.i(TAG, "location : " + location.getLongitude() + ", " + location.getLatitude());
+ if (mLastLocation != null) {
+ Log.i(TAG, "mLastLocation: " + mLastLocation.getLongitude() + ", " +
+ mLastLocation.getLatitude());
+ } // end of deletion
+ */
+
+ mLastLocation = location;
+ }
+
+ private void consolidateClusters(long duration) {
+ Log.e(TAG, "considalating " + mLocClusters.size() + " clusters");
+ LocationCluster cluster;
+
+ // TODO: which should be first? considate or merge?
+ for (int i = mLocClusters.size() - 1; i >= 0; --i) {
+ cluster = mLocClusters.get(i);
+ cluster.consolidate(duration);
+
+ // TODO: currently set threshold to 1 sec so almost none of the location
+ // clusters will be removed.
+ if (!cluster.passThreshold(LOCATION_CLUSTER_THRESHOLD)) {
+ mLocClusters.remove(cluster);
+ }
+ }
+
+ // merge clusters whose regions are overlapped. note that after merge
+ // translates the cluster center but keeps the region size unchanged.
+ for (int i = mLocClusters.size() - 1; i >= 0; --i) {
+ cluster = mLocClusters.get(i);
+ for (int j = i - 1; j >= 0; --j) {
+ float distance = mLocClusters.get(j).distanceToCluster(cluster);
+ if (distance < LOCATION_CLUSTER_RADIUS) {
+ mLocClusters.get(j).absorbCluster(cluster);
+ mLocClusters.remove(cluster);
+ }
+ }
+ }
+ updateSemanticClusters();
+ }
+
+ private void updateSemanticClusters() {
+ // select candidate location clusters
+ ArrayList<LocationCluster> candidates = new ArrayList<LocationCluster>();
+ for (LocationCluster cluster : mLocClusters) {
+ if (cluster.passThreshold(SEMANTIC_CLUSTER_THRESHOLD)) {
+ candidates.add(cluster);
+ }
+ }
+ for (LocationCluster candidate : candidates) {
+ float bestClusterDistance = Float.MAX_VALUE;
+ SemanticCluster bestCluster = null;
+ for (SemanticCluster cluster : mSemanticClusters) {
+ float distance = cluster.distanceToCluster(candidate);
+
+ Log.e(TAG, "distance to semantic cluster: " + cluster.getSemanticId());
+
+ if (distance < bestClusterDistance) {
+ bestClusterDistance = distance;
+ bestCluster = cluster;
+ }
+ }
+
+ // add the location to the selected cluster
+ SemanticCluster semanticCluster;
+ if (bestClusterDistance < SEMANTIC_CLUSTER_RADIUS) {
+ bestCluster.absorbCluster(candidate);
+ } else {
+ // if it is far away from all existing clusters, create a new cluster.
+ semanticCluster = new SemanticCluster(candidate, CONSOLIDATE_INTERVAL,
+ mSemanticClusterCount++);
+ mSemanticClusters.add(semanticCluster);
+ }
+ }
+ Log.e(TAG, "location: " + candidates.size() + ", semantic: " + mSemanticClusters.size());
+
+ candidates.clear();
+ }
+
+ public String getSemanticLocation() {
+ String label = "unknown";
+
+ if (mLastLocation != null) {
+ for (SemanticCluster cluster: mSemanticClusters) {
+ if (cluster.distanceToCenter(mLastLocation) < SEMANTIC_CLUSTER_RADIUS) {
+ return cluster.getSemanticId();
+ }
+ }
+ }
+ return label;
+ }
+}
diff --git a/bordeaux/service/src/android/bordeaux/services/FeatureAssembly.java b/bordeaux/service/src/android/bordeaux/services/FeatureAssembly.java
index 8dae57c..3566950 100644
--- a/bordeaux/service/src/android/bordeaux/services/FeatureAssembly.java
+++ b/bordeaux/service/src/android/bordeaux/services/FeatureAssembly.java
@@ -29,7 +29,7 @@
import android.bordeaux.services.Aggregator;
import java.io.Serializable;
-public class FeatureAssembly {
+class FeatureAssembly {
private static final String TAG = "FeatureAssembly";
private List<String> mPossibleFeatures;
private HashSet<String> mUseFeatures;
@@ -54,6 +54,24 @@
return (Set) mUseFeatures;
}
+ public Map<String, String> getFeatureMap() {
+ HashMap<String, String> featureMap = new HashMap<String, String>();
+
+ Iterator itr = mUseFeatures.iterator();
+ while(itr.hasNext()) {
+ String f = (String) itr.next();
+ Map<String, String> features = mAggregatorManager.getDataMap(f);
+
+ // TODO: sanity check for now.
+ if (features.size() > 1) {
+ throw new RuntimeException("Incorrect feature format extracted from aggregator.");
+ }
+
+ featureMap.putAll(features);
+ }
+ return (Map)featureMap;
+ }
+
public String augmentFeatureInputString(String s) {
String fs = s;
Iterator itr = mUseFeatures.iterator();
diff --git a/bordeaux/service/src/android/bordeaux/services/IPredictor.aidl b/bordeaux/service/src/android/bordeaux/services/IPredictor.aidl
index d2f6036..0986cd0 100644
--- a/bordeaux/service/src/android/bordeaux/services/IPredictor.aidl
+++ b/bordeaux/service/src/android/bordeaux/services/IPredictor.aidl
@@ -16,9 +16,11 @@
package android.bordeaux.services;
+import android.bordeaux.services.StringFloat;
+
interface IPredictor {
- boolean setPredictorParameter( in String s, in String f );
- void pushNewSample(in String s);
- void ResetPredictor();
- float getSampleProbability(in String s);
+ boolean setPredictorParameter(in String key, in String value);
+ void pushNewSample(in String sampleName);
+ void resetPredictor();
+ List<StringFloat> getTopCandidates(in int topK);
}
diff --git a/bordeaux/service/src/android/bordeaux/services/LocationCluster.java b/bordeaux/service/src/android/bordeaux/services/LocationCluster.java
new file mode 100644
index 0000000..9745d13
--- /dev/null
+++ b/bordeaux/service/src/android/bordeaux/services/LocationCluster.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bordeaux.services;
+
+import android.location.Location;
+import android.text.format.Time;
+import android.util.Log;
+
+import java.lang.Math;
+import java.util.ArrayList;
+
+public class LocationCluster extends BaseCluster {
+ public static String TAG = "LocationCluster";
+
+ private static double FORGETTING_FACTOR = 0.1;
+
+ private boolean mIsNewCluster;
+
+ private ArrayList<Location> mLocations = new ArrayList<Location>();
+
+ public LocationCluster(Location location, long avgInterval) {
+ super(location, avgInterval);
+ mIsNewCluster = true;
+ }
+
+ public void addSample(Location location) {
+ mLocations.add(location);
+ }
+
+ public void consolidate(long interval) {
+ // TODO: add check on interval
+ double[] newCenter = {0f, 0f, 0f};
+ long newDuration = 0l;
+
+ // update cluster center
+ for (Location location : mLocations) {
+ double[] vector = getLocationVector(location);
+ long duration = location.getTime();
+
+ newDuration += duration;
+ for (int i = 0; i < 3; ++i) {
+ newCenter[i] += vector[i] * duration;
+ }
+ }
+ for (int i = 0; i < 3; ++i) {
+ newCenter[i] /= newDuration;
+ }
+ // remove location data
+ mLocations.clear();
+
+ if (mIsNewCluster) {
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] = newCenter[i];
+ }
+ mDuration = newDuration;
+ mIsNewCluster = false;
+ } else {
+ // the new center is weight average over latest and existing centers.
+ // fine tune the weight of new center
+ double newWeight = ((double) newDuration) / (newDuration + mDuration);
+ newWeight *= FORGETTING_FACTOR;
+ double currWeight = 1f - newWeight;
+ double norm = 0;
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] = currWeight * mCenter[i] + newWeight * newCenter[i];
+ norm += mCenter[i] * mCenter[i];
+ }
+ // normalize
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] /= norm;
+ }
+
+ newWeight = FORGETTING_FACTOR;
+ currWeight = 1f - newWeight;
+ mDuration = (long) (mDuration * currWeight + newDuration * newWeight);
+ }
+ }
+
+ /*
+ * if the new created cluster whose covered area overlaps with any existing
+ * cluster move the center away from that cluster till there is no overlap.
+ */
+ public void moveAwayCluster(LocationCluster cluster, float distance) {
+ double[] vector = new double[3];
+
+ double dot = 0f;
+ for (int i = 0; i < 3; ++i) {
+ dot += mCenter[i] * cluster.mCenter[i];
+ }
+ double norm = 0f;
+ for (int i = 0; i < 3; ++i) {
+ vector[i] = mCenter[i] - dot * cluster.mCenter[i];
+ norm += vector[i] * vector[i];
+ }
+ norm = Math.sqrt(norm);
+
+ double radian = distance / EARTH_RADIUS;
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] = cluster.mCenter[i] * Math.cos(radian) +
+ (vector[i] / norm) * Math.sin(radian);
+ }
+ }
+}
diff --git a/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java b/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java
index 6294df8..d6512cb 100644
--- a/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java
+++ b/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java
@@ -16,24 +16,127 @@
package android.bordeaux.services;
+import android.content.Context;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
-class LocationStatsAggregator extends Aggregator {
+public class LocationStatsAggregator extends Aggregator {
final String TAG = "LocationStatsAggregator";
public static final String CURRENT_LOCATION = "Current Location";
+
+ private static final long MINIMUM_TIME = 30000; // milliseconds
+ private static final float MINIMUM_DISTANCE = 0f; // meter
+ private static final int LOCATION_CHANGE = 1;
+
+ private static final int BEST_PROVIDER_DURATION = 120000;
+
+ private long mProviderSetTime;
+
+ private final Criteria mCriteria = new Criteria();
+
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private LocationManager mLocationManager;
+ private ClusterManager mClusterManager;
+
+ public LocationStatsAggregator(final Context context) {
+
+ Log.e(TAG, "initialize location manager");
+
+ mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ setClusteringThread();
+
+ requestLocationUpdate();
+ }
+
public String[] getListOfFeatures(){
String [] list = new String[1];
list[0] = CURRENT_LOCATION;
return list;
}
+
public Map<String,String> getFeatureValue(String featureName) {
- HashMap<String,String> m = new HashMap<String,String>();
- if (featureName.equals(CURRENT_LOCATION))
- m.put(CURRENT_LOCATION, "Here"); //TODO put location resutls here
- else
- Log.e(TAG, "There is no Location feature called " + featureName);
- return (Map) m;
+ HashMap<String,String> feature = new HashMap<String,String>();
+ if (featureName.equals(CURRENT_LOCATION)) {
+ feature.put(CURRENT_LOCATION, mClusterManager.getSemanticLocation());
+ }
+ return (Map) feature;
}
+
+ private void setClusteringThread() {
+ mClusterManager = new ClusterManager();
+
+ mHandlerThread = new HandlerThread("Location Handler",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper()) {
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (!(msg.obj instanceof Location)) {
+ return;
+ }
+ Location location = (Location) msg.obj;
+ switch(msg.what) {
+ case LOCATION_CHANGE:
+ mClusterManager.addSample(location);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ };
+ }
+
+ private void requestLocationUpdate() {
+ Criteria criteria = new Criteria();
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+ criteria.setPowerRequirement(Criteria.POWER_LOW);
+ criteria.setAltitudeRequired(false);
+ criteria.setBearingRequired(false);
+ criteria.setSpeedRequired(false);
+ criteria.setCostAllowed(true);
+
+ String bestProvider = mLocationManager.getBestProvider(criteria, true);
+ Log.i(TAG, "Best Location Provider: " + bestProvider);
+
+ mProviderSetTime = System.currentTimeMillis();
+ if (bestProvider != null) {
+ mLocationManager.requestLocationUpdates(
+ bestProvider, MINIMUM_TIME, MINIMUM_DISTANCE, mLocationListener);
+ }
+ }
+
+ private final LocationListener mLocationListener = new LocationListener() {
+ public void onLocationChanged(Location location) {
+ long currentTime = location.getTime();
+ if (currentTime - mProviderSetTime < MINIMUM_TIME) {
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(LOCATION_CHANGE, location));
+ // search again for the location service
+ if (currentTime - mProviderSetTime > BEST_PROVIDER_DURATION) {
+ mLocationManager.removeUpdates(this);
+ Log.e(TAG, "reselect best location provider");
+ requestLocationUpdate();
+ }
+ }
+
+ public void onStatusChanged(String provider, int status, Bundle extras) { }
+
+ public void onProviderEnabled(String provider) { }
+
+ public void onProviderDisabled(String provider) { }
+ };
}
diff --git a/bordeaux/service/src/android/bordeaux/services/MotionStatsAggregator.java b/bordeaux/service/src/android/bordeaux/services/MotionStatsAggregator.java
index c9344fd..c34779e 100644
--- a/bordeaux/service/src/android/bordeaux/services/MotionStatsAggregator.java
+++ b/bordeaux/service/src/android/bordeaux/services/MotionStatsAggregator.java
@@ -20,7 +20,7 @@
import java.util.HashMap;
import java.util.Map;
-class MotionStatsAggregator extends Aggregator {
+public class MotionStatsAggregator extends Aggregator {
final String TAG = "MotionStatsAggregator";
public static final String CURRENT_MOTION = "Current Motion";
public String[] getListOfFeatures(){
diff --git a/bordeaux/service/src/android/bordeaux/services/Predictor.java b/bordeaux/service/src/android/bordeaux/services/Predictor.java
index 8bfd82e..6369340 100644
--- a/bordeaux/service/src/android/bordeaux/services/Predictor.java
+++ b/bordeaux/service/src/android/bordeaux/services/Predictor.java
@@ -2,7 +2,7 @@
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
+ * you my not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
@@ -20,6 +20,7 @@
import android.util.Log;
import java.util.HashMap;
import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.HashSet;
import java.util.Iterator;
@@ -27,7 +28,7 @@
import java.io.*;
import java.lang.Boolean;
import android.bordeaux.services.FeatureAssembly;
-import android.bordeaux.learning.predictorHist;
+import android.bordeaux.learning.HistogramPredictor;
/**
* This is interface to implement Prediction based on histogram that
@@ -35,176 +36,91 @@
*/
public class Predictor extends IPredictor.Stub
implements IBordeauxLearner {
- private ModelChangeCallback modelChangeCallback = null;
private final String TAG = "Predictor";
- private final String SET_EXPIRE_TIME = "SetExpireTime";
- private final String USE_HISTORY = "Use History";
- private final String SET_FEATURE = "Set Feature";
- private long mExpireTime = 3 * 60;
- private long mLastSampleTime = 0;
- private boolean mUseHistoryFlag = false;
- private final String NEW_START = "New Start";
+ private ModelChangeCallback modelChangeCallback = null;
- static public class Model implements Serializable {
- public HashMap<String, Integer> countHistogram = new HashMap<String, Integer>();
- public HashSet<String> usedFeatures = new HashSet<String>();
- public int sampleCounts;
- public boolean useHistoryFlag;
- public long expireTime;
- public long lastSampleTime;
- }
+ private HistogramPredictor mPredictor = new HistogramPredictor();
+ private FeatureAssembly mFeatureAssembly = new FeatureAssembly();
- private predictorHist mPredictorHist = new predictorHist();
- private String mLastSample = NEW_START;
- public FeatureAssembly mFeatureAssembly = new FeatureAssembly();
+ public static final String SET_FEATURE = "set feature";
+
/**
* Reset the Predictor
*/
- public void ResetPredictor(){
- printModel(getPredictionModel());
- mPredictorHist.ResetPredictorHist();
- mUseHistoryFlag = false;
- mLastSampleTime = 0;
- mLastSample = NEW_START;
- mFeatureAssembly = new FeatureAssembly();
- printModel(getPredictionModel());
+ public void resetPredictor(){
+ mPredictor.resetPredictor();
+
if (modelChangeCallback != null) {
modelChangeCallback.modelChanged(this);
}
}
/**
- * Augment input string with buildin features such as time, location
- */
- private String buildDataPoint(String sampleName) {
- String fs = mFeatureAssembly.augmentFeatureInputString(sampleName);
- if (mUseHistoryFlag) {
- if (((System.currentTimeMillis()- mLastSampleTime)/1000) > mExpireTime) {
- mLastSample = NEW_START;
- }
- fs = fs + "+" + mLastSample;
- }
- return fs;
- }
-
- /**
* Input is a sampleName e.g.action name. This input is then augmented with requested build-in
* features such as time and location to create sampleFeatures. The sampleFeatures is then
* pushed to the histogram
*/
public void pushNewSample(String sampleName) {
- String sampleFeatures = buildDataPoint(sampleName);
- mLastSample = sampleName;
- mLastSampleTime = System.currentTimeMillis();
- mPredictorHist.pushSample(sampleFeatures);
+ Map<String, String> sampleFeatures = mFeatureAssembly.getFeatureMap();
+ Log.e(TAG, "pushNewSample " + sampleName + ": " + sampleFeatures);
+
+ mPredictor.addSample(sampleName, sampleFeatures);
if (modelChangeCallback != null) {
modelChangeCallback.modelChanged(this);
}
- //printModel(getPredictionModel());
}
+
+ // TODO: getTopK samples instead get scord for debugging only
/**
* return probabilty of an exmple using the histogram
*/
- public float getSampleProbability(String sampleName) {
- String sampleFeatures = buildDataPoint(sampleName);
- return mPredictorHist.getProbability(sampleFeatures);
+ public List<StringFloat> getTopCandidates(int topK) {
+ ArrayList<StringFloat> result = new ArrayList<StringFloat>(topK);
+ Map<String, String> features = mFeatureAssembly.getFeatureMap();
+
+ List<Map.Entry<String, Double> > topApps = mPredictor.findTopClasses(features, topK);
+
+ int listSize = topApps.size();
+ if (topK > 0) {
+ listSize = Math.min(topK, listSize);
+ }
+
+ for (int i = 0; i < listSize; ++i) {
+ Map.Entry<String, Double> entry = topApps.get(i);
+ result.add(new StringFloat(entry.getKey(), entry.getValue().floatValue()));
+ }
+ return result;
}
+
/**
* Set parameters for 1) using History in probability estimations e.g. consider the last event
* and 2) featureAssembly e.g. time and location.
*/
- public boolean setPredictorParameter(String s, String f) {
- boolean res = false;
- if (s.equals(USE_HISTORY)) {
- if (f.equals("true")){
- mUseHistoryFlag = true;
- res = true;
+ public boolean setPredictorParameter(String key, String value) {
+ boolean result = true;
+ if (key.equals(SET_FEATURE)) {
+ result = mFeatureAssembly.registerFeature(value);
+ if (result) {
+ mPredictor.useFeature(value);
+ } else {
+ Log.e(TAG,"Setting on feauture: " + value + " which is not available");
}
- else if (f.equals("false")) {
- mUseHistoryFlag = false;
- res = true;
- }
- } else if (s.equals(SET_EXPIRE_TIME)) {
- mExpireTime = Long.valueOf(f);
- res = true;
- } else if (s.equals(SET_FEATURE)) {
- res = mFeatureAssembly.registerFeature(f);
+ } else {
+ Log.e(TAG,"Setting parameter " + key + " with " + value + " is not valid");
}
- if (!res)
- Log.e(TAG,"Setting parameter " + s + " with " + f + " is not valid");
- return res;
- }
-
- public Model getPredictionModel() {
- Model m = new Model();
- m.countHistogram.putAll(mPredictorHist.getHist());
- m.sampleCounts = mPredictorHist.getHistCounts();
- m.expireTime = mExpireTime;
- m.usedFeatures = (HashSet) mFeatureAssembly.getUsedFeatures();
- m.useHistoryFlag = mUseHistoryFlag;
- m.lastSampleTime = mLastSampleTime;
- return m;
- }
-
- public boolean loadModel(Model m) {
- //Log.i(TAG,"on loadModel");
- //printModel(m);
- mPredictorHist = new predictorHist();
- mPredictorHist.set(m.countHistogram);
- mExpireTime = m.expireTime;
- mUseHistoryFlag = m.useHistoryFlag;
- mLastSampleTime = m.lastSampleTime;
- mFeatureAssembly = new FeatureAssembly();
- boolean res = false;
- Iterator itr = m.usedFeatures.iterator();
- while(itr.hasNext()) {
- res = res & mFeatureAssembly.registerFeature((String) itr.next());
- }
- return res;
- }
-
- public void printModel(Model m) {
- Log.i(TAG,"histogram is : " + m.countHistogram.toString());
- Log.i(TAG,"number of counts in histogram is : " + m.sampleCounts);
- Log.i(TAG,"ExpireTime time is : " + m.expireTime);
- Log.i(TAG,"useHistoryFlag is : " + m.useHistoryFlag);
- Log.i(TAG,"used features are : " + m.usedFeatures.toString());
+ return result;
}
// Beginning of the IBordeauxLearner Interface implementation
public byte [] getModel() {
- Model model = getPredictionModel();
- //Log.i(TAG,"on getModel");
- printModel(model);
- try {
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- ObjectOutputStream objStream = new ObjectOutputStream(byteStream);
- objStream.writeObject(model);
- byte[] bytes = byteStream.toByteArray();
- //Log.i(TAG, "getModel: " + bytes);
- return bytes;
- } catch (IOException e) {
- throw new RuntimeException("Can't get model");
- }
+ return mPredictor.getModel();
}
public boolean setModel(final byte [] modelData) {
- //Log.i(TAG,"on setModel");
- try {
- ByteArrayInputStream input = new ByteArrayInputStream(modelData);
- ObjectInputStream objStream = new ObjectInputStream(input);
- Model model = (Model) objStream.readObject();
- boolean res = loadModel(model);
- //Log.i(TAG, "LoadModel: " + modelData);
- return res;
- } catch (IOException e) {
- throw new RuntimeException("Can't load model");
- } catch (ClassNotFoundException e) {
- throw new RuntimeException("Learning class not found");
- }
+ return mPredictor.setModel(modelData);
}
public IBinder getBinder() {
diff --git a/bordeaux/service/src/android/bordeaux/services/SemanticCluster.java b/bordeaux/service/src/android/bordeaux/services/SemanticCluster.java
new file mode 100644
index 0000000..cfc028c
--- /dev/null
+++ b/bordeaux/service/src/android/bordeaux/services/SemanticCluster.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bordeaux.services;
+
+import android.location.Location;
+import android.text.format.Time;
+import android.util.Log;
+
+import java.lang.Math;
+import java.util.ArrayList;
+
+public class SemanticCluster extends BaseCluster {
+
+ public static String TAG = "SemanticCluster";
+
+ private String mSemanticId;
+
+ public SemanticCluster(LocationCluster cluster, long avgInterval, long semanticIndex) {
+ mCenter = new double[3];
+ for (int i = 0; i < 3; ++i) {
+ mCenter[i] = cluster.mCenter[i];
+ }
+ mDuration = cluster.mDuration;
+ mAvgInterval = avgInterval;
+
+ setSemanticId(semanticIndex);
+ }
+
+ public String getSemanticId() {
+ return mSemanticId;
+ }
+
+ private void setSemanticId(long index) {
+ mSemanticId = "cluser: " + String.valueOf(index);
+ }
+}
diff --git a/bordeaux/service/src/android/bordeaux/services/StringFloat.java b/bordeaux/service/src/android/bordeaux/services/StringFloat.java
index 0a3d22d..c95ccd2 100644
--- a/bordeaux/service/src/android/bordeaux/services/StringFloat.java
+++ b/bordeaux/service/src/android/bordeaux/services/StringFloat.java
@@ -21,6 +21,11 @@
public StringFloat() {
}
+ public StringFloat(String newKey, float newValue) {
+ key = newKey;
+ value = newValue;
+ }
+
private StringFloat(Parcel in) {
readFromParcel(in);
}
diff --git a/bordeaux/service/src/android/bordeaux/services/TimeStatsAggregator.java b/bordeaux/service/src/android/bordeaux/services/TimeStatsAggregator.java
index 8626189..377d9c3 100644
--- a/bordeaux/service/src/android/bordeaux/services/TimeStatsAggregator.java
+++ b/bordeaux/service/src/android/bordeaux/services/TimeStatsAggregator.java
@@ -16,52 +16,120 @@
package android.bordeaux.services;
-import java.util.Date;
+import android.text.format.Time;
import android.util.Log;
+
import java.util.HashMap;
import java.util.Map;
-class TimeStatsAggregator extends Aggregator {
+// import java.util.Date;
+
+// TODO: use build in functions in
+// import android.text.format.Time;
+public class TimeStatsAggregator extends Aggregator {
final String TAG = "TimeStatsAggregator";
- public static final String CURRENT_TIME = "Current Time";
- final String EARLY_MORNING = "EarlyMorning";
- final String MORNING = "Morning";
- final String NOON = "Noon";
- final String AFTERNOON = "AfterNoon";
- final String NIGHT = "Night";
- final String LATE_NIGHT = "LateNight";
+
+ public static final String TIME_OF_WEEK = "Time of Week";
+ public static final String DAY_OF_WEEK = "Day of Week";
+ public static final String TIME_OF_DAY = "Time of Day";
+ public static final String PERIOD_OF_DAY = "Period of Day";
+
+ static final String WEEKEND = "Weekend";
+ static final String WEEKDAY = "Weekday";
+ static final String MONDAY = "Monday";
+ static final String TUESDAY = "Tuesday";
+ static final String WEDNESDAY = "Wednesday";
+ static final String THURSDAY = "Tuesday";
+ static final String FRIDAY = "Friday";
+ static final String SATURDAY = "Saturday";
+ static final String SUNDAY = "Sunday";
+ static final String MORNING = "Morning";
+ static final String NOON = "Noon";
+ static final String AFTERNOON = "AfterNoon";
+ static final String EVENING = "Evening";
+ static final String NIGHT = "Night";
+ static final String LATENIGHT = "LateNight";
+ static final String DAYTIME = "Daytime";
+ static final String NIGHTTIME = "Nighttime";
+
+ final Time mTime = new Time();
+ final HashMap<String, String> mFeatures = new HashMap<String, String>();
public String[] getListOfFeatures(){
- String [] list = new String[1];
- list[0] = CURRENT_TIME;
+ String [] list = new String[4];
+ list[0] = TIME_OF_WEEK;
+ list[1] = DAY_OF_WEEK;
+ list[2] = TIME_OF_DAY;
+ list[3] = PERIOD_OF_DAY;
return list;
}
public Map<String,String> getFeatureValue(String featureName) {
- HashMap<String,String> m = new HashMap<String,String>();
- if (featureName.equals(CURRENT_TIME))
- m.put(CURRENT_TIME, getCurrentTimeLabel());
- else
+ HashMap<String,String> feature = new HashMap<String,String>();
+
+ updateFeatures();
+ if (mFeatures.containsKey(featureName)) {
+ feature.put(featureName, mFeatures.get(featureName));
+ } else {
Log.e(TAG, "There is no Time feature called " + featureName);
- return (Map) m;
+ }
+ return (Map)feature;
}
- private String getCurrentTimeLabel(){
- Date d = new Date(System.currentTimeMillis());
- String t = ""; //TODO maybe learn thresholds
- int h = d.getHours();
- if ((h > 5) & (h <= 7) )
- t = EARLY_MORNING;
- else if ((h > 7) & (h <= 11) )
- t = MORNING;
- else if ((h > 11) & (h <= 15))
- t = NOON;
- else if ((h > 15) & (h <= 20))
- t = AFTERNOON;
- else if ((h > 20) & (h <= 24))
- t = NIGHT;
- else if ((h > 0) & (h <= 5))
- t = LATE_NIGHT;
- return t;
+ private void updateFeatures() {
+ mFeatures.clear();
+ mTime.set(System.currentTimeMillis());
+
+ switch (mTime.weekDay) {
+ case Time.SATURDAY:
+ mFeatures.put(DAY_OF_WEEK, SATURDAY);
+ break;
+ case Time.SUNDAY:
+ mFeatures.put(DAY_OF_WEEK, SUNDAY);
+ break;
+ case Time.MONDAY:
+ mFeatures.put(DAY_OF_WEEK, MONDAY);
+ break;
+ case Time.TUESDAY:
+ mFeatures.put(DAY_OF_WEEK, TUESDAY);
+ break;
+ case Time.WEDNESDAY:
+ mFeatures.put(DAY_OF_WEEK, WEDNESDAY);
+ break;
+ case Time.THURSDAY:
+ mFeatures.put(DAY_OF_WEEK, THURSDAY);
+ break;
+ default:
+ mFeatures.put(DAY_OF_WEEK, FRIDAY);
+ }
+
+ if (mTime.hour > 6 && mTime.hour < 19) {
+ mFeatures.put(PERIOD_OF_DAY, DAYTIME);
+ } else {
+ mFeatures.put(PERIOD_OF_DAY, NIGHTTIME);
+ }
+
+ if (mTime.hour >= 5 && mTime.hour < 12) {
+ mFeatures.put(TIME_OF_DAY, MORNING);
+ } else if (mTime.hour >= 12 && mTime.hour < 14) {
+ mFeatures.put(TIME_OF_DAY, NOON);
+ } else if (mTime.hour >= 14 && mTime.hour < 18) {
+ mFeatures.put(TIME_OF_DAY, AFTERNOON);
+ } else if (mTime.hour >= 18 && mTime.hour < 22) {
+ mFeatures.put(TIME_OF_DAY, EVENING);
+ } else if ((mTime.hour >= 22 && mTime.hour < 24) ||
+ (mTime.hour >= 0 && mTime.hour < 1)) {
+ mFeatures.put(TIME_OF_DAY, NIGHT);
+ } else {
+ mFeatures.put(TIME_OF_DAY, LATENIGHT);
+ }
+
+ if (mTime.weekDay == Time.SUNDAY || mTime.weekDay == Time.SATURDAY ||
+ (mTime.weekDay == Time.FRIDAY &&
+ mFeatures.get(PERIOD_OF_DAY).equals(NIGHTTIME))) {
+ mFeatures.put(TIME_OF_WEEK, WEEKEND);
+ } else {
+ mFeatures.put(TIME_OF_WEEK, WEEKDAY);
+ }
}
}