GeofenceManager.java revision e0fd693c6098f59004f9e96ad75c058e26c337b0
1/*
2 * Copyright (C) 20012 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 com.android.server.location;
18
19import java.io.PrintWriter;
20import java.util.Iterator;
21import java.util.LinkedList;
22import java.util.List;
23
24import android.Manifest.permission;
25import android.app.PendingIntent;
26import android.content.Context;
27import android.content.Intent;
28import android.location.Location;
29import android.location.LocationListener;
30import android.location.LocationManager;
31import android.os.Bundle;
32import android.os.Looper;
33import android.os.PowerManager;
34import android.os.SystemClock;
35
36public class GeofenceManager implements LocationListener, PendingIntent.OnFinished {
37    static final String TAG = "GeofenceManager";
38
39    /**
40     * Assume a maximum land speed, as a heuristic to throttle location updates.
41     * (Air travel should result in an airplane mode toggle which will
42     * force a new location update anyway).
43     */
44    static final int MAX_SPEED_M_S = 100;  // 360 km/hr (high speed train)
45
46    class GeofenceWrapper {
47        final Geofence fence;
48        final long expiry;
49        final String packageName;
50        final PendingIntent intent;
51
52        public GeofenceWrapper(Geofence fence, long expiry, String packageName,
53                PendingIntent intent) {
54            this.fence = fence;
55            this.expiry = expiry;
56            this.packageName = packageName;
57            this.intent = intent;
58        }
59    }
60
61    final Context mContext;
62    final LocationManager mLocationManager;
63    final PowerManager.WakeLock mWakeLock;
64    final Looper mLooper;  // looper thread to take location updates on
65
66    // access to members below is synchronized on this
67    Location mLastLocation;
68    List<GeofenceWrapper> mFences = new LinkedList<GeofenceWrapper>();
69
70    public GeofenceManager(Context context) {
71        mContext = context;
72        mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
73        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
74        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
75        mLooper = Looper.myLooper();
76        mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
77    }
78
79    public void addFence(double latitude, double longitude, float radius, long expiration,
80            PendingIntent intent, int uid, String packageName) {
81        long expiry = SystemClock.elapsedRealtime() + expiration;
82        if (expiration < 0) {
83            expiry = Long.MAX_VALUE;
84        }
85        Geofence fence = new Geofence(latitude, longitude, radius, mLastLocation);
86        GeofenceWrapper fenceWrapper = new GeofenceWrapper(fence, expiry, packageName, intent);
87
88        synchronized (this) {
89            mFences.add(fenceWrapper);
90            updateProviderRequirements();
91        }
92    }
93
94    public void removeFence(PendingIntent intent) {
95        synchronized (this) {
96            Iterator<GeofenceWrapper> iter = mFences.iterator();
97            while (iter.hasNext()) {
98                GeofenceWrapper fenceWrapper = iter.next();
99                if (fenceWrapper.intent.equals(intent)) {
100                    iter.remove();
101                }
102            }
103            updateProviderRequirements();
104        }
105    }
106
107    public void removeFence(String packageName) {
108        synchronized (this) {
109            Iterator<GeofenceWrapper> iter = mFences.iterator();
110            while (iter.hasNext()) {
111                GeofenceWrapper fenceWrapper = iter.next();
112                if (fenceWrapper.packageName.equals(packageName)) {
113                    iter.remove();
114                }
115            }
116            updateProviderRequirements();
117        }
118    }
119
120    void removeExpiredFences() {
121        synchronized (this) {
122            long time = SystemClock.elapsedRealtime();
123            Iterator<GeofenceWrapper> iter = mFences.iterator();
124            while (iter.hasNext()) {
125                GeofenceWrapper fenceWrapper = iter.next();
126                if (fenceWrapper.expiry < time) {
127                    iter.remove();
128                }
129            }
130        }
131    }
132
133    void processLocation(Location location) {
134        List<PendingIntent> enterIntents = new LinkedList<PendingIntent>();
135        List<PendingIntent> exitIntents = new LinkedList<PendingIntent>();
136
137        synchronized (this) {
138            mLastLocation = location;
139
140            removeExpiredFences();
141
142            for (GeofenceWrapper fenceWrapper : mFences) {
143                int event = fenceWrapper.fence.processLocation(location);
144                if ((event & Geofence.FLAG_ENTER) != 0) {
145                    enterIntents.add(fenceWrapper.intent);
146                }
147                if ((event & Geofence.FLAG_EXIT) != 0) {
148                    exitIntents.add(fenceWrapper.intent);
149                }
150            }
151            updateProviderRequirements();
152        }
153
154        // release lock before sending intents
155        for (PendingIntent intent : exitIntents) {
156            sendIntentExit(intent);
157        }
158        for (PendingIntent intent : enterIntents) {
159            sendIntentEnter(intent);
160        }
161    }
162
163    void sendIntentEnter(PendingIntent pendingIntent) {
164        Intent intent = new Intent();
165        intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
166        sendIntent(pendingIntent, intent);
167    }
168
169    void sendIntentExit(PendingIntent pendingIntent) {
170        Intent intent = new Intent();
171        intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
172        sendIntent(pendingIntent, intent);
173    }
174
175    void sendIntent(PendingIntent pendingIntent, Intent intent) {
176        try {
177            mWakeLock.acquire();
178            pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION);
179        } catch (PendingIntent.CanceledException e) {
180            removeFence(pendingIntent);
181            mWakeLock.release();
182        }
183    }
184
185    void updateProviderRequirements() {
186        synchronized (this) {
187            double minDistance = Double.MAX_VALUE;
188            for (GeofenceWrapper alert : mFences) {
189                if (alert.fence.getDistance() < minDistance) {
190                    minDistance = alert.fence.getDistance();
191                }
192            }
193
194            if (minDistance == Double.MAX_VALUE) {
195                disableLocation();
196            } else {
197                int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S;
198                setLocationInterval(intervalMs);
199            }
200        }
201    }
202
203    void setLocationInterval(int intervalMs) {
204        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, intervalMs, 0, this,
205                mLooper);
206    }
207
208    void disableLocation() {
209        mLocationManager.removeUpdates(this);
210    }
211
212    @Override
213    public void onLocationChanged(Location location) {
214        processLocation(location);
215    }
216
217    @Override
218    public void onStatusChanged(String provider, int status, Bundle extras) { }
219
220    @Override
221    public void onProviderEnabled(String provider) { }
222
223    @Override
224    public void onProviderDisabled(String provider) { }
225
226    @Override
227    public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
228            String resultData, Bundle resultExtras) {
229        mWakeLock.release();
230    }
231
232    public void dump(PrintWriter pw) {
233        pw.println("  Geofences:");
234        for (GeofenceWrapper fenceWrapper : mFences) {
235            pw.append("    ");
236            pw.append(fenceWrapper.packageName);
237            pw.append(" ");
238            pw.append(fenceWrapper.fence.toString());
239            pw.append("\n");
240        }
241    }
242}
243