Added Fake location support.

Change-Id: I894a135bf15f6d776f06b5e1c6b59db64ae745c1
diff --git a/bordeaux/service/src/android/bordeaux/services/AggregatorManager.java b/bordeaux/service/src/android/bordeaux/services/AggregatorManager.java
index 42ccf9f..c314ee7 100644
--- a/bordeaux/service/src/android/bordeaux/services/AggregatorManager.java
+++ b/bordeaux/service/src/android/bordeaux/services/AggregatorManager.java
@@ -20,15 +20,21 @@
 import android.bordeaux.services.StringString;
 import android.content.Context;
 import android.util.Log;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 class AggregatorManager extends IAggregatorManager.Stub  {
     private final String TAG = "AggregatorMnager";
-    private static HashMap<String, Aggregator> sFeatureMap;
+    // this maps from the aggregator name to the registered aggregator instance
+    private static HashMap<String, Aggregator> mAggregators = new HashMap<String, Aggregator>();
+    // this maps from the feature names to the aggregator that generates the feature
+    private static HashMap<String, Aggregator> sFeatureMap = new HashMap<String, Aggregator>();
     private static AggregatorManager mManager = null;
 
+    private String mFakeLocation = null;
+
     private AggregatorManager() {
         sFeatureMap = new HashMap<String, Aggregator>();
     }
@@ -50,16 +56,49 @@
     }
 
     public void registerAggregator(Aggregator agg, AggregatorManager m) {
+        if (mAggregators.get(agg.getClass().getName()) != null) {
+            // only one instance
+            throw new RuntimeException("Can't register more than one instance");
+        }
+        mAggregators.put(agg.getClass().getName(), agg);
         agg.setManager(m);
         String[] fl = agg.getListOfFeatures();
         for ( int i  = 0; i< fl.length; i ++)
             sFeatureMap.put(fl[i], agg);
     }
-
+    // Start of IAggregatorManager interface
     public ArrayList<StringString> getData(String dataName) {
         return getList(getDataMap(dataName));
     }
 
+    public List<String> getLocationClusters() {
+        LocationStatsAggregator agg = (LocationStatsAggregator)
+                mAggregators.get(LocationStatsAggregator.class.getName());
+        if (agg == null) return new ArrayList<String> ();
+        ArrayList<String> clusters = new ArrayList<String>();
+        return agg.getClusterNames();
+    }
+
+    // Set an empty string "" to disable the fake location
+    public boolean setFakeLocation(String location) {
+        LocationStatsAggregator agg = (LocationStatsAggregator)
+                mAggregators.get(LocationStatsAggregator.class.getName());
+        if (agg == null) return false;
+        agg.setFakeLocation(location);
+        mFakeLocation = location;
+        return true;
+    }
+
+    // Get the current mode, if fake mode return true
+    public boolean getFakeMode() {
+        boolean fakeMode = false;
+        // checking any features that are in the fake mode
+        if (mFakeLocation != null && mFakeLocation.length() != 0)
+            fakeMode = true;
+        return fakeMode;
+    }
+    // End of IAggregatorManger interface
+
     public Map<String, String> getDataMap(String dataName) {
         if (sFeatureMap.get(dataName) != null)
             return sFeatureMap.get(dataName).getFeatureValue(dataName);
diff --git a/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java b/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java
index 7e2346f..659e847 100644
--- a/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java
+++ b/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java
@@ -61,6 +61,39 @@
         }
     }
 
+    public List<String> getLocationClusters() {
+        if (!retrieveAggregatorManager())
+            throw new RuntimeException(AggregatorManager_NOTAVAILABLE);
+        try {
+            return mAggregatorManager.getLocationClusters();
+        } catch (RemoteException e) {
+            Log.e(TAG,"Error getting location clusters");
+            throw new RuntimeException(AggregatorManager_NOTAVAILABLE);
+        }
+    }
+
+    public boolean setFakeLocation(final String name) {
+        if (!retrieveAggregatorManager())
+            throw new RuntimeException(AggregatorManager_NOTAVAILABLE);
+        try {
+            return mAggregatorManager.setFakeLocation(name);
+        } catch (RemoteException e) {
+            Log.e(TAG,"Error setting fake location:" + name);
+            throw new RuntimeException(AggregatorManager_NOTAVAILABLE);
+        }
+    }
+
+    public boolean getFakeMode() {
+        if (!retrieveAggregatorManager())
+            throw new RuntimeException(AggregatorManager_NOTAVAILABLE);
+        try {
+            return mAggregatorManager.getFakeMode();
+        } catch (RemoteException e) {
+            Log.e(TAG,"Error getting fake mode");
+            throw new RuntimeException(AggregatorManager_NOTAVAILABLE);
+        }
+    }
+
     private Map<String, String> getMap(final List<StringString> sample) {
         HashMap<String, String> map = new HashMap<String, String>();
         for (int i =0; i < sample.size(); i++) {
diff --git a/bordeaux/service/src/android/bordeaux/services/ClusterManager.java b/bordeaux/service/src/android/bordeaux/services/ClusterManager.java
index f6217e3..b5d0b1a 100644
--- a/bordeaux/service/src/android/bordeaux/services/ClusterManager.java
+++ b/bordeaux/service/src/android/bordeaux/services/ClusterManager.java
@@ -191,27 +191,29 @@
         List<Map<String, String> > allData = mStorage.getAllData();
         HashMap<String, Long> histogram = new HashMap<String, Long>();
 
-        mSemanticClusters.clear();
-        for (Map<String, String> map : allData) {
-            String semanticId = map.get(SEMANTIC_ID);
-            double longitude = Double.valueOf(map.get(SEMANTIC_LONGITUDE));
-            double latitude = Double.valueOf(map.get(SEMANTIC_LATITUDE));
-            SemanticCluster cluster = new SemanticCluster(
-                    semanticId, longitude, latitude, CONSOLIDATE_INTERVAL);
+        synchronized (mSemanticClusters) {
+            mSemanticClusters.clear();
+            for (Map<String, String> map : allData) {
+                String semanticId = map.get(SEMANTIC_ID);
+                double longitude = Double.valueOf(map.get(SEMANTIC_LONGITUDE));
+                double latitude = Double.valueOf(map.get(SEMANTIC_LATITUDE));
+                SemanticCluster cluster = new SemanticCluster(
+                        semanticId, longitude, latitude, CONSOLIDATE_INTERVAL);
 
-            histogram.clear();
-            for (int i = mFeatureValueStart; i <= mFeatureValueEnd; i++) {
-                String featureValue = SEMANTIC_COLUMNS[i];
-                if (map.containsKey(featureValue)) {
-                    histogram.put(featureValue, Long.valueOf(map.get(featureValue)));
+                histogram.clear();
+                for (int i = mFeatureValueStart; i <= mFeatureValueEnd; i++) {
+                    String featureValue = SEMANTIC_COLUMNS[i];
+                    if (map.containsKey(featureValue)) {
+                        histogram.put(featureValue, Long.valueOf(map.get(featureValue)));
+                    }
                 }
+                cluster.setHistogram(histogram);
+
+                mSemanticClusters.add(cluster);
             }
-            cluster.setHistogram(histogram);
 
-            mSemanticClusters.add(cluster);
+            mSemanticClusterCount = mSemanticClusters.size();
         }
-
-        mSemanticClusterCount = mSemanticClusters.size();
         Log.e(TAG, "load " + mSemanticClusterCount + " semantic clusters.");
     }
 
@@ -219,76 +221,80 @@
         HashMap<String, String> rowFeatures = new HashMap<String, String>();
         Log.e(TAG, "save " + mSemanticClusters.size() + " semantic clusters.");
 
-        mStorage.removeAllData();
-        for (SemanticCluster cluster : mSemanticClusters) {
-            rowFeatures.clear();
-            rowFeatures.put(SEMANTIC_ID, cluster.getSemanticId());
+        synchronized (mSemanticClusters) {
+            mStorage.removeAllData();
+            for (SemanticCluster cluster : mSemanticClusters) {
+                rowFeatures.clear();
+                rowFeatures.put(SEMANTIC_ID, cluster.getSemanticId());
 
-            rowFeatures.put(SEMANTIC_LONGITUDE,
-                            String.valueOf(cluster.getCenterLongitude()));
-            rowFeatures.put(SEMANTIC_LATITUDE,
-                            String.valueOf(cluster.getCenterLatitude()));
+                rowFeatures.put(SEMANTIC_LONGITUDE,
+                                String.valueOf(cluster.getCenterLongitude()));
+                rowFeatures.put(SEMANTIC_LATITUDE,
+                                String.valueOf(cluster.getCenterLatitude()));
 
-            HashMap<String, Long> histogram = cluster.getHistogram();
-            for (Map.Entry<String, Long> entry : histogram.entrySet()) {
-                rowFeatures.put(entry.getKey(), String.valueOf(entry.getValue()));
+                HashMap<String, Long> histogram = cluster.getHistogram();
+                for (Map.Entry<String, Long> entry : histogram.entrySet()) {
+                    rowFeatures.put(entry.getKey(), String.valueOf(entry.getValue()));
+                }
+                mStorage.addData(rowFeatures);
             }
-            mStorage.addData(rowFeatures);
         }
     }
 
     private void updateSemanticClusters() {
         HashMap<String, ArrayList<BaseCluster> > semanticMap =
                 new HashMap<String, ArrayList<BaseCluster> >();
-        for (SemanticCluster cluster : mSemanticClusters) {
-            String semanticId = cluster.getSemanticId();
-            semanticMap.put(cluster.getSemanticId(), new ArrayList<BaseCluster>());
-            semanticMap.get(semanticId).add(cluster);
-        }
-
-        // select candidate location clusters
-        ArrayList<LocationCluster> candidates = new ArrayList<LocationCluster>();
-        for (LocationCluster cluster : mLocClusters) {
-            if (cluster.passThreshold(SEMANTIC_CLUSTER_THRESHOLD)) {
-                candidates.add(cluster);
-            }
-        }
-
-        // assign each candidate to a semantic cluster
-        for (LocationCluster candidate : candidates) {
-            if (candidate.hasSemanticId()) {
-                // candidate has been assigned to a semantic cluster
-                continue;
-            }
-
-            // find the closest semantic cluster
-            float bestClusterDistance = Float.MAX_VALUE;
-            SemanticCluster bestCluster = null;
+        synchronized (mSemanticClusters) {
             for (SemanticCluster cluster : mSemanticClusters) {
-                float distance = cluster.distanceToCluster(candidate);
-                Log.e(TAG, "distance to semantic cluster: " + cluster.getSemanticId());
+                String semanticId = cluster.getSemanticId();
+                semanticMap.put(cluster.getSemanticId(), new ArrayList<BaseCluster>());
+                semanticMap.get(semanticId).add(cluster);
+            }
 
-                if (distance < bestClusterDistance) {
-                    bestClusterDistance = distance;
-                    bestCluster = cluster;
+            // select candidate location clusters
+            ArrayList<LocationCluster> candidates = new ArrayList<LocationCluster>();
+            for (LocationCluster cluster : mLocClusters) {
+                if (cluster.passThreshold(SEMANTIC_CLUSTER_THRESHOLD)) {
+                    candidates.add(cluster);
                 }
             }
 
-            // add the location to the selected cluster
-            SemanticCluster semanticCluster;
-            if (bestClusterDistance > SEMANTIC_CLUSTER_RADIUS) {
-                // if it is far away from all existing clusters, create a new cluster.
-                bestCluster = new SemanticCluster(candidate, CONSOLIDATE_INTERVAL,
-                                                  mSemanticClusterCount++);
-                String id = bestCluster.getSemanticId();
-                semanticMap.put(id, new ArrayList<BaseCluster>());
-                semanticMap.get(id).add(bestCluster);
+            // assign each candidate to a semantic cluster
+            for (LocationCluster candidate : candidates) {
+                if (candidate.hasSemanticId()) {
+                    // candidate has been assigned to a semantic cluster
+                    continue;
+                }
+
+                // find the closest semantic cluster
+                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) {
+                    // if it is far away from all existing clusters, create a new cluster.
+                    bestCluster = new SemanticCluster(candidate, CONSOLIDATE_INTERVAL,
+                                                      mSemanticClusterCount++);
+                    String id = bestCluster.getSemanticId();
+                    semanticMap.put(id, new ArrayList<BaseCluster>());
+                    semanticMap.get(id).add(bestCluster);
+                }
+                String semanticId = bestCluster.getSemanticId();
+                candidate.setSemanticId(semanticId);
+                semanticMap.get(semanticId).add(candidate);
             }
-            String semanticId = bestCluster.getSemanticId();
-            candidate.setSemanticId(semanticId);
-            semanticMap.get(semanticId).add(candidate);
+            candidates.clear();
         }
-        candidates.clear();
         Log.e(TAG, "number of semantic clusters: " + semanticMap.size());
 
         // use candidates semantic cluster
@@ -316,12 +322,24 @@
         // instead of using the last location, try acquiring the latest location.
         if (mLastLocation != null) {
             // TODO: use fast neatest neighbor search speed up location search
-            for (SemanticCluster cluster: mSemanticClusters) {
-                if (cluster.distanceToCenter(mLastLocation) < SEMANTIC_CLUSTER_RADIUS) {
-                    return cluster.getSemanticId();
+            synchronized (mSemanticClusters) {
+                for (SemanticCluster cluster: mSemanticClusters) {
+                    if (cluster.distanceToCenter(mLastLocation) < SEMANTIC_CLUSTER_RADIUS) {
+                        return cluster.getSemanticId();
+                    }
                 }
             }
         }
         return label;
     }
+
+    public List<String> getClusterNames() {
+        ArrayList<String> clusters = new ArrayList<String>();
+        synchronized (mSemanticClusters) {
+            for (SemanticCluster cluster: mSemanticClusters) {
+                clusters.add(cluster.getSemanticId());
+            }
+        }
+        return clusters;
+    }
 }
diff --git a/bordeaux/service/src/android/bordeaux/services/IAggregatorManager.aidl b/bordeaux/service/src/android/bordeaux/services/IAggregatorManager.aidl
index 65028be..1b2f067 100644
--- a/bordeaux/service/src/android/bordeaux/services/IAggregatorManager.aidl
+++ b/bordeaux/service/src/android/bordeaux/services/IAggregatorManager.aidl
@@ -20,4 +20,11 @@
 
 interface IAggregatorManager {
     List<StringString> getData(in String dataName);
+
+    // TODO: remove the following interfaces in production
+    // they are only used for demo purpose
+    List<String> getLocationClusters();
+    // use "" to disable the fake location
+    boolean setFakeLocation(in String cluster);
+    boolean getFakeMode();
 }
diff --git a/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java b/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java
index 0c4dddb..0c05157 100644
--- a/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java
+++ b/bordeaux/service/src/android/bordeaux/services/LocationStatsAggregator.java
@@ -29,6 +29,7 @@
 import android.os.Process;
 import android.util.Log;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 // TODO: add functionality to detect speed (use GPS) when needed
@@ -57,6 +58,9 @@
     private LocationManager mLocationManager;
     private ClusterManager mClusterManager;
 
+    // Fake location, used for testing.
+    private String mFakeLocation = null;
+
     public LocationStatsAggregator(final Context context) {
         mLocationManager =
             (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
@@ -82,12 +86,28 @@
 
             String location = mClusterManager.getSemanticLocation();
             if (!location.equals(UNKNOWN_LOCATION)) {
-                feature.put(CURRENT_LOCATION, location);
+                if (mFakeLocation != null) {
+                    feature.put(CURRENT_LOCATION, mFakeLocation);
+                } else {
+                    feature.put(CURRENT_LOCATION, location);
+                }
             }
         }
         return (Map) feature;
     }
 
+    public List<String> getClusterNames() {
+        return mClusterManager.getClusterNames();
+    }
+
+    // set a fake location using cluster name.
+    // Set an empty string "" to disable the fake location
+    public void setFakeLocation(String name) {
+        if (name != null && name.length() != 0)
+            mFakeLocation = name;
+        else mFakeLocation = null;
+    }
+
     private void setClusteringThread(Context context) {
         mClusterManager = new ClusterManager(context);