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