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);
+        }
     }
 }