| /* |
| * 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.app.AlarmManager; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| 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.os.SystemClock; |
| import android.text.format.Time; |
| import android.util.Log; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| // TODO: add functionality to detect speed (use GPS) when needed |
| // withouth draining the battery quickly |
| public class LocationStatsAggregator extends Aggregator { |
| final String TAG = "LocationStatsAggregator"; |
| public static final String CURRENT_LOCATION = "Current Location"; |
| public static final String CURRENT_SPEED = "Current Speed"; |
| public static final String UNKNOWN_LOCATION = "Unknown Location"; |
| |
| private static final long REPEAT_INTERVAL = 120000; |
| |
| private static final long FRESH_THRESHOLD = 90000; |
| |
| private static final int LOCATION_CHANGE = 1; |
| |
| // record time when the location provider is set |
| private long mProviderSetTime; |
| |
| private Handler mHandler; |
| private HandlerThread mHandlerThread; |
| private AlarmManager mAlarmManager; |
| private LocationManager mLocationManager; |
| |
| private ClusterManager mClusterManager; |
| |
| private Criteria mCriteria = new Criteria(); |
| |
| private LocationUpdater mLocationUpdater; |
| |
| private Context mContext; |
| private PendingIntent mPendingIntent; |
| |
| // Fake location, used for testing. |
| private String mFakeLocation = null; |
| |
| public LocationStatsAggregator(final Context context) { |
| mLocationManager = |
| (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); |
| mAlarmManager = |
| (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
| |
| setClusteringThread(context); |
| |
| mCriteria.setAccuracy(Criteria.ACCURACY_COARSE); |
| mCriteria.setPowerRequirement(Criteria.POWER_LOW); |
| /* |
| mCriteria.setAltitudeRequired(false); |
| mCriteria.setBearingRequired(false); |
| mCriteria.setSpeedRequired(true); |
| */ |
| mCriteria.setCostAllowed(true); |
| |
| |
| IntentFilter filter = new IntentFilter(LocationUpdater.LOCATION_UPDATE); |
| mLocationUpdater = new LocationUpdater(); |
| context.registerReceiver(mLocationUpdater, filter); |
| |
| Intent intent = new Intent(LocationUpdater.LOCATION_UPDATE); |
| |
| mContext = context; |
| mPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); |
| |
| mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| SystemClock.elapsedRealtime() + 30000, // |
| REPEAT_INTERVAL, |
| mPendingIntent); |
| } |
| |
| public void release() { |
| mContext.unregisterReceiver(mLocationUpdater); |
| mAlarmManager.cancel(mPendingIntent); |
| } |
| |
| public String[] getListOfFeatures(){ |
| String[] list = { CURRENT_LOCATION } ; |
| return list; |
| } |
| |
| public Map<String,String> getFeatureValue(String featureName) { |
| HashMap<String,String> feature = new HashMap<String,String>(); |
| |
| if (featureName.equals(CURRENT_LOCATION)) { |
| // TODO: check last known location first before sending out location request. |
| /* |
| Location location = |
| mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); |
| */ |
| String location = mClusterManager.getSemanticLocation(); |
| if (!location.equals(UNKNOWN_LOCATION)) { |
| if (mFakeLocation != null) { |
| feature.put(CURRENT_LOCATION, mFakeLocation); |
| } else { |
| feature.put(CURRENT_LOCATION, location); |
| } |
| } |
| } |
| return (Map) feature; |
| } |
| |
| public List<String> getClusterNames() { |
| return mClusterManager.getClusterNames(); |
| } |
| |
| // set a fake location using cluster name. |
| // Set an empty string "" to disable the fake location |
| public void setFakeLocation(String name) { |
| if (name != null && name.length() != 0) |
| mFakeLocation = name; |
| else mFakeLocation = null; |
| } |
| |
| private Location getLastKnownLocation() { |
| List<String> providers = mLocationManager.getAllProviders(); |
| Location bestResult = null; |
| float bestAccuracy = Float.MAX_VALUE; |
| long bestTime; |
| |
| // get the latest location data |
| long currTime = System.currentTimeMillis(); |
| for (String provider : providers) { |
| Location location = mLocationManager.getLastKnownLocation(provider); |
| |
| if (location != null) { |
| float accuracy = location.getAccuracy(); |
| long time = location.getTime(); |
| |
| if (currTime - time < FRESH_THRESHOLD && accuracy < bestAccuracy) { |
| bestResult = location; |
| bestAccuracy = accuracy; |
| bestTime = time; |
| } |
| } |
| } |
| if (bestResult != null) { |
| Log.i(TAG, "found location for free: " + bestResult); |
| } |
| return bestResult; |
| } |
| |
| private class LocationUpdater extends BroadcastReceiver { |
| String TAG = "LocationUpdater"; |
| |
| public static final String LOCATION_UPDATE = "android.bordeaux.services.LOCATION_UPDATE"; |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Location location = getLastKnownLocation(); |
| |
| if (location == null) { |
| String provider = mLocationManager.getBestProvider(mCriteria, true); |
| Log.i(TAG, "Best Available Location Provider: " + provider); |
| mLocationManager.requestSingleUpdate(provider, mLocationListener, |
| mHandlerThread.getLooper()); |
| } else { |
| mHandler.sendMessage(mHandler.obtainMessage(LOCATION_CHANGE, location)); |
| } |
| } |
| } |
| |
| private void setClusteringThread(Context context) { |
| mClusterManager = new ClusterManager(context); |
| |
| 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 final LocationListener mLocationListener = new LocationListener() { |
| private static final String TAG = "LocationListener"; |
| |
| public void onLocationChanged(Location location) { |
| mHandler.sendMessage(mHandler.obtainMessage(LOCATION_CHANGE, location)); |
| mLocationManager.removeUpdates(this); |
| } |
| |
| public void onStatusChanged(String provider, int status, Bundle extras) { } |
| |
| public void onProviderEnabled(String provider) { } |
| |
| public void onProviderDisabled(String provider) { } |
| }; |
| } |