1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.bordeaux.services; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.location.Criteria; 26import android.location.Location; 27import android.location.LocationListener; 28import android.location.LocationManager; 29import android.location.LocationProvider; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.HandlerThread; 33import android.os.Message; 34import android.os.Process; 35import android.os.SystemClock; 36import android.text.format.Time; 37import android.util.Log; 38import java.util.HashMap; 39import java.util.List; 40import java.util.Map; 41 42// TODO: add functionality to detect speed (use GPS) when needed 43// withouth draining the battery quickly 44public class LocationStatsAggregator extends Aggregator { 45 final String TAG = "LocationStatsAggregator"; 46 public static final String CURRENT_LOCATION = "Current Location"; 47 public static final String CURRENT_SPEED = "Current Speed"; 48 public static final String UNKNOWN_LOCATION = "Unknown Location"; 49 50 private static final long REPEAT_INTERVAL = 120000; 51 52 private static final long FRESH_THRESHOLD = 90000; 53 54 private static final int LOCATION_CHANGE = 1; 55 56 // record time when the location provider is set 57 private long mProviderSetTime; 58 59 private Handler mHandler; 60 private HandlerThread mHandlerThread; 61 private AlarmManager mAlarmManager; 62 private LocationManager mLocationManager; 63 64 private ClusterManager mClusterManager; 65 66 private Criteria mCriteria = new Criteria(); 67 68 private LocationUpdater mLocationUpdater; 69 70 private Context mContext; 71 private PendingIntent mPendingIntent; 72 73 // Fake location, used for testing. 74 private String mFakeLocation = null; 75 76 public LocationStatsAggregator(final Context context) { 77 mLocationManager = 78 (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 79 mAlarmManager = 80 (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 81 82 setClusteringThread(context); 83 84 mCriteria.setAccuracy(Criteria.ACCURACY_COARSE); 85 mCriteria.setPowerRequirement(Criteria.POWER_LOW); 86 /* 87 mCriteria.setAltitudeRequired(false); 88 mCriteria.setBearingRequired(false); 89 mCriteria.setSpeedRequired(true); 90 */ 91 mCriteria.setCostAllowed(true); 92 93 94 IntentFilter filter = new IntentFilter(LocationUpdater.LOCATION_UPDATE); 95 mLocationUpdater = new LocationUpdater(); 96 context.registerReceiver(mLocationUpdater, filter); 97 98 Intent intent = new Intent(LocationUpdater.LOCATION_UPDATE); 99 100 mContext = context; 101 mPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 102 103 mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 104 SystemClock.elapsedRealtime() + 30000, // 105 REPEAT_INTERVAL, 106 mPendingIntent); 107 } 108 109 public void release() { 110 mContext.unregisterReceiver(mLocationUpdater); 111 mAlarmManager.cancel(mPendingIntent); 112 } 113 114 public String[] getListOfFeatures(){ 115 String[] list = { CURRENT_LOCATION } ; 116 return list; 117 } 118 119 public Map<String,String> getFeatureValue(String featureName) { 120 HashMap<String,String> feature = new HashMap<String,String>(); 121 122 if (featureName.equals(CURRENT_LOCATION)) { 123 // TODO: check last known location first before sending out location request. 124 /* 125 Location location = 126 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); 127 */ 128 String location = mClusterManager.getSemanticLocation(); 129 if (!location.equals(UNKNOWN_LOCATION)) { 130 if (mFakeLocation != null) { 131 feature.put(CURRENT_LOCATION, mFakeLocation); 132 } else { 133 feature.put(CURRENT_LOCATION, location); 134 } 135 } 136 } 137 return (Map) feature; 138 } 139 140 public List<String> getClusterNames() { 141 return mClusterManager.getClusterNames(); 142 } 143 144 // set a fake location using cluster name. 145 // Set an empty string "" to disable the fake location 146 public void setFakeLocation(String name) { 147 if (name != null && name.length() != 0) 148 mFakeLocation = name; 149 else mFakeLocation = null; 150 } 151 152 private Location getLastKnownLocation() { 153 List<String> providers = mLocationManager.getAllProviders(); 154 Location bestResult = null; 155 float bestAccuracy = Float.MAX_VALUE; 156 long bestTime; 157 158 // get the latest location data 159 long currTime = System.currentTimeMillis(); 160 for (String provider : providers) { 161 Location location = mLocationManager.getLastKnownLocation(provider); 162 163 if (location != null) { 164 float accuracy = location.getAccuracy(); 165 long time = location.getTime(); 166 167 if (currTime - time < FRESH_THRESHOLD && accuracy < bestAccuracy) { 168 bestResult = location; 169 bestAccuracy = accuracy; 170 bestTime = time; 171 } 172 } 173 } 174 if (bestResult != null) { 175 Log.i(TAG, "found location for free: " + bestResult); 176 } 177 return bestResult; 178 } 179 180 private class LocationUpdater extends BroadcastReceiver { 181 String TAG = "LocationUpdater"; 182 183 public static final String LOCATION_UPDATE = "android.bordeaux.services.LOCATION_UPDATE"; 184 185 @Override 186 public void onReceive(Context context, Intent intent) { 187 Location location = getLastKnownLocation(); 188 189 if (location == null) { 190 String provider = mLocationManager.getBestProvider(mCriteria, true); 191 Log.i(TAG, "Best Available Location Provider: " + provider); 192 mLocationManager.requestSingleUpdate(provider, mLocationListener, 193 mHandlerThread.getLooper()); 194 } else { 195 mHandler.sendMessage(mHandler.obtainMessage(LOCATION_CHANGE, location)); 196 } 197 } 198 } 199 200 private void setClusteringThread(Context context) { 201 mClusterManager = new ClusterManager(context); 202 203 mHandlerThread = new HandlerThread("Location Handler", 204 Process.THREAD_PRIORITY_BACKGROUND); 205 mHandlerThread.start(); 206 mHandler = new Handler(mHandlerThread.getLooper()) { 207 208 @Override 209 public void handleMessage(Message msg) { 210 if (!(msg.obj instanceof Location)) { 211 return; 212 } 213 Location location = (Location) msg.obj; 214 switch(msg.what) { 215 case LOCATION_CHANGE: 216 mClusterManager.addSample(location); 217 break; 218 default: 219 super.handleMessage(msg); 220 } 221 } 222 }; 223 } 224 225 private final LocationListener mLocationListener = new LocationListener() { 226 private static final String TAG = "LocationListener"; 227 228 public void onLocationChanged(Location location) { 229 mHandler.sendMessage(mHandler.obtainMessage(LOCATION_CHANGE, location)); 230 mLocationManager.removeUpdates(this); 231 } 232 233 public void onStatusChanged(String provider, int status, Bundle extras) { } 234 235 public void onProviderEnabled(String provider) { } 236 237 public void onProviderDisabled(String provider) { } 238 }; 239} 240