diff options
author | Ruei-sung Lin <rslin@google.com> | 2012-08-13 19:04:29 -0700 |
---|---|---|
committer | Ruei-sung Lin <rslin@google.com> | 2012-08-23 10:25:23 -0700 |
commit | f0f78449e8ab7d63894964c54b6ef390ca9ce044 (patch) | |
tree | 73ca768d621701eafd30f604aa9d54a2b620cd1c | |
parent | 984e52f31d596840cfa51b1238e1c43d2e1918f8 (diff) | |
download | ml-f0f78449e8ab7d63894964c54b6ef390ca9ce044.tar.gz |
implement location aggregator
Change-Id: I48a02766741474fa33a3f56e60db12db114a032d
18 files changed, 1065 insertions, 213 deletions
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 000000000..d79afc548 --- /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 index 7924424eb..c332be560 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 @@ import android.util.Log; * 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 @@ public class predictorHist { //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 class predictorHist { */ 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 class predictorHist { */ 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 0359fc606..5e5ec3f2c 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 000000000..1d595f75a --- /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 a26e9cdc6..7e2346f2d 100644 --- a/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java +++ b/bordeaux/service/src/android/bordeaux/services/BordeauxAggregatorManager.java @@ -35,11 +35,12 @@ public class BordeauxAggregatorManager { 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 @@ public class BordeauxAggregatorManager { } 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 65ffdda80..00fbb30f8 100644 --- a/bordeaux/service/src/android/bordeaux/services/BordeauxManagerService.java +++ b/bordeaux/service/src/android/bordeaux/services/BordeauxManagerService.java @@ -57,7 +57,6 @@ public class BordeauxManagerService { 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 cd0e57eaf..54d96a2c3 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.bordeaux.services.IPredictor; 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 @@ public class BordeauxPredictor { 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 @@ public class BordeauxPredictor { 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 16bb78f2d..ab6f0f116 100644 --- a/bordeaux/service/src/android/bordeaux/services/BordeauxService.java +++ b/bordeaux/service/src/android/bordeaux/services/BordeauxService.java @@ -75,7 +75,7 @@ public class BordeauxService extends Service { //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 000000000..88ba1f3ab --- /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 8dae57c65..3566950d2 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.AggregatorManager; 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 @@ public class FeatureAssembly { 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 d2f6036f0..0986cd0a7 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 000000000..9745d1314 --- /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 6294df8e1..d6512cb24 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 c9344fda8..c34779e5e 100644 --- a/bordeaux/service/src/android/bordeaux/services/MotionStatsAggregator.java +++ b/bordeaux/service/src/android/bordeaux/services/MotionStatsAggregator.java @@ -20,7 +20,7 @@ import android.util.Log; 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 8bfd82e78..6369340d6 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.os.IBinder; 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.Serializable; 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 @@ import android.bordeaux.learning.predictorHist; */ 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"; - - 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 ModelChangeCallback modelChangeCallback = null; + + private HistogramPredictor mPredictor = new HistogramPredictor(); + private FeatureAssembly mFeatureAssembly = new FeatureAssembly(); + + public static final String SET_FEATURE = "set feature"; - private predictorHist mPredictorHist = new predictorHist(); - private String mLastSample = NEW_START; - public FeatureAssembly mFeatureAssembly = new FeatureAssembly(); /** * 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); - } - 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()); + } else { + Log.e(TAG,"Setting parameter " + key + " with " + value + " is not valid"); } - 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 000000000..cfc028c7f --- /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 0a3d22d15..c95ccd29c 100644 --- a/bordeaux/service/src/android/bordeaux/services/StringFloat.java +++ b/bordeaux/service/src/android/bordeaux/services/StringFloat.java @@ -21,6 +21,11 @@ public final class StringFloat implements Parcelable { 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 86261890a..377d9c3f3 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); + } } } |