/* * 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 getFeatureValue(String featureName) { HashMap feature = new HashMap(); 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 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 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) { } }; }