blob: 095cd146da4f046879e8a7ce79e85636034849e3 [file] [log] [blame]
Nick Pellye0fd6932012-07-11 10:26:13 -07001/*
Victoria Lease4cd0a502012-11-02 16:24:08 -07002 * Copyright (C) 2012 The Android Open Source Project
Nick Pellye0fd6932012-07-11 10:26:13 -07003 *
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
Dianne Hackborn5e45ee62013-01-24 19:13:44 -080019import android.app.AppOpsManager;
Nick Pellye0fd6932012-07-11 10:26:13 -070020import android.app.PendingIntent;
21import android.content.Context;
22import android.content.Intent;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070023import android.location.Geofence;
Nick Pellye0fd6932012-07-11 10:26:13 -070024import android.location.Location;
25import android.location.LocationListener;
26import android.location.LocationManager;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070027import android.location.LocationRequest;
Nick Pellye0fd6932012-07-11 10:26:13 -070028import android.os.Bundle;
Victoria Lease4cd0a502012-11-02 16:24:08 -070029import android.os.Handler;
Soonil Nagarkarb8466b72019-10-25 14:10:30 -070030import android.os.Looper;
Victoria Lease4cd0a502012-11-02 16:24:08 -070031import android.os.Message;
Nick Pellye0fd6932012-07-11 10:26:13 -070032import android.os.PowerManager;
33import android.os.SystemClock;
Soonil Nagarkarb8466b72019-10-25 14:10:30 -070034import android.util.Log;
Victoria Lease4cd0a502012-11-02 16:24:08 -070035import android.util.Slog;
Nick Pelly4035f5a2012-08-17 14:43:49 -070036
Soonil Nagarkarb8466b72019-10-25 14:10:30 -070037import com.android.server.FgThread;
Lifu Tang519f0d02018-04-12 16:39:39 -070038import com.android.server.PendingIntentUtils;
Nick Pellye0fd6932012-07-11 10:26:13 -070039
Soonil Nagarkar1c572552019-07-10 13:31:47 -070040import java.io.PrintWriter;
41import java.util.Iterator;
42import java.util.LinkedList;
43import java.util.List;
44
Nick Pellye0fd6932012-07-11 10:26:13 -070045public class GeofenceManager implements LocationListener, PendingIntent.OnFinished {
Nick Pelly6fa9ad42012-07-16 12:18:23 -070046 private static final String TAG = "GeofenceManager";
Soonil Nagarkarb8466b72019-10-25 14:10:30 -070047 private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
Nick Pellye0fd6932012-07-11 10:26:13 -070048
Victoria Lease4cd0a502012-11-02 16:24:08 -070049 private static final int MSG_UPDATE_FENCES = 1;
50
Nick Pellye0fd6932012-07-11 10:26:13 -070051 /**
52 * Assume a maximum land speed, as a heuristic to throttle location updates.
53 * (Air travel should result in an airplane mode toggle which will
54 * force a new location update anyway).
55 */
Nick Pelly6fa9ad42012-07-16 12:18:23 -070056 private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train)
Nick Pellye0fd6932012-07-11 10:26:13 -070057
Victoria Lease4cd0a502012-11-02 16:24:08 -070058 /**
59 * Maximum age after which a location is no longer considered fresh enough to use.
60 */
61 private static final long MAX_AGE_NANOS = 5 * 60 * 1000000000L; // five minutes
62
Victoria Lease4cd0a502012-11-02 16:24:08 -070063
64 /**
65 * Least frequent update interval allowed.
66 */
67 private static final long MAX_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
68
Nick Pelly6fa9ad42012-07-16 12:18:23 -070069 private final Context mContext;
Soonil Nagarkarb8466b72019-10-25 14:10:30 -070070 private final GeofenceHandler mHandler;
71
Nick Pelly6fa9ad42012-07-16 12:18:23 -070072 private final LocationManager mLocationManager;
Dianne Hackborn5e45ee62013-01-24 19:13:44 -080073 private final AppOpsManager mAppOps;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070074 private final PowerManager.WakeLock mWakeLock;
Soonil Nagarkarb8466b72019-10-25 14:10:30 -070075
Soonil Nagarkarb6375a42020-01-29 15:23:06 -080076 private final SettingsHelper mSettingsStore;
Nick Pellye0fd6932012-07-11 10:26:13 -070077
Soonil Nagarkar1c572552019-07-10 13:31:47 -070078 private final Object mLock = new Object();
Nick Pellye0fd6932012-07-11 10:26:13 -070079
Nick Pelly6fa9ad42012-07-16 12:18:23 -070080 // access to members below is synchronized on mLock
Victoria Lease4cd0a502012-11-02 16:24:08 -070081 /**
82 * A list containing all registered geofences.
83 */
Soonil Nagarkar1c572552019-07-10 13:31:47 -070084 private List<GeofenceState> mFences = new LinkedList<>();
Nick Pellye0fd6932012-07-11 10:26:13 -070085
Victoria Lease4cd0a502012-11-02 16:24:08 -070086 /**
87 * This is set true when we have an active request for {@link Location} updates via
88 * {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener,
89 * android.os.Looper).
90 */
91 private boolean mReceivingLocationUpdates;
92
93 /**
94 * The update interval component of the current active {@link Location} update request.
95 */
96 private long mLocationUpdateInterval;
97
98 /**
99 * The {@link Location} most recently received via {@link #onLocationChanged(Location)}.
100 */
101 private Location mLastLocationUpdate;
102
103 /**
104 * This is set true when a {@link Location} is received via
105 * {@link #onLocationChanged(Location)} or {@link #scheduleUpdateFencesLocked()}, and cleared
106 * when that Location has been processed via {@link #updateFences()}
107 */
108 private boolean mPendingUpdate;
109
Soonil Nagarkarb6375a42020-01-29 15:23:06 -0800110 public GeofenceManager(Context context, SettingsHelper settingsStore) {
Nick Pellye0fd6932012-07-11 10:26:13 -0700111 mContext = context;
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700112 mHandler = new GeofenceHandler(FgThread.getHandler().getLooper());
Lifu Tangc94ef4d2017-03-23 23:48:00 -0700113
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700114 mLocationManager = mContext.getSystemService(LocationManager.class);
115 mAppOps = mContext.getSystemService(AppOpsManager.class);
116
117 mWakeLock = mContext.getSystemService(PowerManager.class).newWakeLock(
118 PowerManager.PARTIAL_WAKE_LOCK, TAG);
119
120 mSettingsStore = settingsStore;
Nick Pellye0fd6932012-07-11 10:26:13 -0700121 }
122
Victoria Lease4cd0a502012-11-02 16:24:08 -0700123 public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
Soonil Nagarkar39766392020-03-28 13:59:26 -0700124 CallerIdentity identity) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700125 GeofenceState state = new GeofenceState(geofence,
Soonil Nagarkar95768ce2019-11-05 15:22:44 -0800126 request.getExpirationRealtimeMs(SystemClock.elapsedRealtime()),
Soonil Nagarkar39766392020-03-28 13:59:26 -0700127 identity, intent);
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700128 synchronized (mLock) {
129 // first make sure it doesn't already exist
130 for (int i = mFences.size() - 1; i >= 0; i--) {
131 GeofenceState w = mFences.get(i);
132 if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) {
133 // already exists, remove the old one
134 mFences.remove(i);
135 break;
Nick Pellye0fd6932012-07-11 10:26:13 -0700136 }
137 }
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700138 mFences.add(state);
Victoria Lease4cd0a502012-11-02 16:24:08 -0700139 scheduleUpdateFencesLocked();
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700140 }
141 }
142
143 public void removeFence(Geofence fence, PendingIntent intent) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700144 if (D) {
145 Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent);
146 }
147
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700148 synchronized (mLock) {
149 Iterator<GeofenceState> iter = mFences.iterator();
150 while (iter.hasNext()) {
151 GeofenceState state = iter.next();
152 if (state.mIntent.equals(intent)) {
153
154 if (fence == null) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700155 // always remove
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700156 iter.remove();
157 } else {
158 // just remove matching fences
159 if (fence.equals(state.mFence)) {
160 iter.remove();
161 }
162 }
163 }
164 }
Victoria Lease4cd0a502012-11-02 16:24:08 -0700165 scheduleUpdateFencesLocked();
Nick Pellye0fd6932012-07-11 10:26:13 -0700166 }
167 }
168
169 public void removeFence(String packageName) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700170 if (D) {
171 Slog.d(TAG, "removeFence: packageName=" + packageName);
172 }
173
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700174 synchronized (mLock) {
Soonil Nagarkar39766392020-03-28 13:59:26 -0700175 mFences.removeIf(state -> state.mIdentity.packageName.equals(packageName));
Victoria Lease4cd0a502012-11-02 16:24:08 -0700176 scheduleUpdateFencesLocked();
Nick Pellye0fd6932012-07-11 10:26:13 -0700177 }
178 }
179
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700180 private void removeExpiredFencesLocked() {
181 long time = SystemClock.elapsedRealtime();
Soonil Nagarkar39766392020-03-28 13:59:26 -0700182 mFences.removeIf(state -> state.mExpireAt < time);
Nick Pellye0fd6932012-07-11 10:26:13 -0700183 }
184
Victoria Lease4cd0a502012-11-02 16:24:08 -0700185 private void scheduleUpdateFencesLocked() {
186 if (!mPendingUpdate) {
187 mPendingUpdate = true;
188 mHandler.sendEmptyMessage(MSG_UPDATE_FENCES);
189 }
190 }
191
192 /**
193 * Returns the location received most recently from {@link #onLocationChanged(Location)},
194 * or consult {@link LocationManager#getLastLocation()} if none has arrived. Does not return
195 * either if the location would be too stale to be useful.
196 *
197 * @return a fresh, valid Location, or null if none is available
198 */
199 private Location getFreshLocationLocked() {
200 // Prefer mLastLocationUpdate to LocationManager.getLastLocation().
201 Location location = mReceivingLocationUpdates ? mLastLocationUpdate : null;
202 if (location == null && !mFences.isEmpty()) {
203 location = mLocationManager.getLastLocation();
204 }
205
206 // Early out for null location.
207 if (location == null) {
208 return null;
209 }
210
211 // Early out for stale location.
212 long now = SystemClock.elapsedRealtimeNanos();
213 if (now - location.getElapsedRealtimeNanos() > MAX_AGE_NANOS) {
214 return null;
215 }
216
217 // Made it this far? Return our fresh, valid location.
218 return location;
219 }
220
221 /**
222 * The geofence update loop. This function removes expired fences, then tests the most
223 * recently-received {@link Location} against each registered {@link GeofenceState}, sending
224 * {@link Intent}s for geofences that have been tripped. It also adjusts the active location
225 * update request with {@link LocationManager} as appropriate for any active geofences.
226 */
227 // Runs on the handler.
228 private void updateFences() {
Soonil Nagarkar1c572552019-07-10 13:31:47 -0700229 List<PendingIntent> enterIntents = new LinkedList<>();
230 List<PendingIntent> exitIntents = new LinkedList<>();
Nick Pellye0fd6932012-07-11 10:26:13 -0700231
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700232 synchronized (mLock) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700233 mPendingUpdate = false;
234
235 // Remove expired fences.
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700236 removeExpiredFencesLocked();
Nick Pellye0fd6932012-07-11 10:26:13 -0700237
Victoria Lease4cd0a502012-11-02 16:24:08 -0700238 // Get a location to work with, either received via onLocationChanged() or
239 // via LocationManager.getLastLocation().
240 Location location = getFreshLocationLocked();
241
242 // Update all fences.
243 // Keep track of the distance to the nearest fence.
244 double minFenceDistance = Double.MAX_VALUE;
245 boolean needUpdates = false;
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700246 for (GeofenceState state : mFences) {
Soonil Nagarkar39766392020-03-28 13:59:26 -0700247 CallerIdentity identity = state.mIdentity;
248 if (mSettingsStore.isLocationPackageBlacklisted(identity.userId,
249 identity.packageName)) {
Nick Pelly4035f5a2012-08-17 14:43:49 -0700250 continue;
251 }
252
Soonil Nagarkar39766392020-03-28 13:59:26 -0700253 int op = CallerIdentity.asAppOp(identity.permissionLevel);
Dianne Hackborn5e45ee62013-01-24 19:13:44 -0800254 if (op >= 0) {
Soonil Nagarkar39766392020-03-28 13:59:26 -0700255 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, identity.uid,
Soonil Nagarkara2f847e2020-04-15 14:24:07 -0700256 identity.packageName, identity.featureId, identity.listenerId)
Philip P. Moltmannbc8b48a2019-09-27 17:06:25 -0700257 != AppOpsManager.MODE_ALLOWED) {
Dianne Hackborn5e45ee62013-01-24 19:13:44 -0800258 continue;
259 }
260 }
261
Victoria Lease4cd0a502012-11-02 16:24:08 -0700262 needUpdates = true;
263 if (location != null) {
264 int event = state.processLocation(location);
265 if ((event & GeofenceState.FLAG_ENTER) != 0) {
266 enterIntents.add(state.mIntent);
267 }
268 if ((event & GeofenceState.FLAG_EXIT) != 0) {
269 exitIntents.add(state.mIntent);
270 }
271
272 // FIXME: Ideally this code should take into account the accuracy of the
273 // location fix that was used to calculate the distance in the first place.
274 double fenceDistance = state.getDistanceToBoundary(); // MAX_VALUE if unknown
275 if (fenceDistance < minFenceDistance) {
276 minFenceDistance = fenceDistance;
277 }
Nick Pellye0fd6932012-07-11 10:26:13 -0700278 }
279 }
Victoria Lease4cd0a502012-11-02 16:24:08 -0700280
281 // Request or cancel location updates if needed.
282 if (needUpdates) {
283 // Request location updates.
284 // Compute a location update interval based on the distance to the nearest fence.
285 long intervalMs;
286 if (location != null && Double.compare(minFenceDistance, Double.MAX_VALUE) != 0) {
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700287 intervalMs = (long) Math.min(MAX_INTERVAL_MS, Math.max(
288 mSettingsStore.getBackgroundThrottleProximityAlertIntervalMs(),
Victoria Lease4cd0a502012-11-02 16:24:08 -0700289 minFenceDistance * 1000 / MAX_SPEED_M_S));
290 } else {
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700291 intervalMs = mSettingsStore.getBackgroundThrottleProximityAlertIntervalMs();
Victoria Lease4cd0a502012-11-02 16:24:08 -0700292 }
293 if (!mReceivingLocationUpdates || mLocationUpdateInterval != intervalMs) {
294 mReceivingLocationUpdates = true;
295 mLocationUpdateInterval = intervalMs;
296 mLastLocationUpdate = location;
297
298 LocationRequest request = new LocationRequest();
299 request.setInterval(intervalMs).setFastestInterval(0);
300 mLocationManager.requestLocationUpdates(request, this, mHandler.getLooper());
301 }
302 } else {
303 // Cancel location updates.
304 if (mReceivingLocationUpdates) {
305 mReceivingLocationUpdates = false;
306 mLocationUpdateInterval = 0;
307 mLastLocationUpdate = null;
308
309 mLocationManager.removeUpdates(this);
310 }
311 }
312
313 if (D) {
314 Slog.d(TAG, "updateFences: location=" + location
315 + ", mFences.size()=" + mFences.size()
316 + ", mReceivingLocationUpdates=" + mReceivingLocationUpdates
317 + ", mLocationUpdateInterval=" + mLocationUpdateInterval
318 + ", mLastLocationUpdate=" + mLastLocationUpdate);
319 }
Nick Pellye0fd6932012-07-11 10:26:13 -0700320 }
321
322 // release lock before sending intents
323 for (PendingIntent intent : exitIntents) {
324 sendIntentExit(intent);
325 }
326 for (PendingIntent intent : enterIntents) {
327 sendIntentEnter(intent);
328 }
329 }
330
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700331 private void sendIntentEnter(PendingIntent pendingIntent) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700332 if (D) {
333 Slog.d(TAG, "sendIntentEnter: pendingIntent=" + pendingIntent);
334 }
335
Nick Pellye0fd6932012-07-11 10:26:13 -0700336 Intent intent = new Intent();
337 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
338 sendIntent(pendingIntent, intent);
339 }
340
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700341 private void sendIntentExit(PendingIntent pendingIntent) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700342 if (D) {
343 Slog.d(TAG, "sendIntentExit: pendingIntent=" + pendingIntent);
344 }
345
Nick Pellye0fd6932012-07-11 10:26:13 -0700346 Intent intent = new Intent();
347 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
348 sendIntent(pendingIntent, intent);
349 }
350
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700351 private void sendIntent(PendingIntent pendingIntent, Intent intent) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700352 mWakeLock.acquire();
Nick Pellye0fd6932012-07-11 10:26:13 -0700353 try {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700354 pendingIntent.send(mContext, 0, intent, this, null,
Lifu Tang519f0d02018-04-12 16:39:39 -0700355 android.Manifest.permission.ACCESS_FINE_LOCATION,
356 PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
Nick Pellye0fd6932012-07-11 10:26:13 -0700357 } catch (PendingIntent.CanceledException e) {
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700358 removeFence(null, pendingIntent);
Nick Pellye0fd6932012-07-11 10:26:13 -0700359 mWakeLock.release();
360 }
Victoria Lease4cd0a502012-11-02 16:24:08 -0700361 // ...otherwise, mWakeLock.release() gets called by onSendFinished()
Nick Pellye0fd6932012-07-11 10:26:13 -0700362 }
363
Victoria Lease4cd0a502012-11-02 16:24:08 -0700364 // Runs on the handler (which was passed into LocationManager.requestLocationUpdates())
Nick Pellye0fd6932012-07-11 10:26:13 -0700365 @Override
366 public void onLocationChanged(Location location) {
Victoria Lease4cd0a502012-11-02 16:24:08 -0700367 synchronized (mLock) {
368 if (mReceivingLocationUpdates) {
369 mLastLocationUpdate = location;
370 }
371
372 // Update the fences immediately before returning in
373 // case the caller is holding a wakelock.
374 if (mPendingUpdate) {
375 mHandler.removeMessages(MSG_UPDATE_FENCES);
376 } else {
377 mPendingUpdate = true;
378 }
379 }
380 updateFences();
Nick Pellye0fd6932012-07-11 10:26:13 -0700381 }
382
383 @Override
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700384 public void onStatusChanged(String provider, int status, Bundle extras) {
385 }
Nick Pellye0fd6932012-07-11 10:26:13 -0700386
387 @Override
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700388 public void onProviderEnabled(String provider) {
389 }
Nick Pellye0fd6932012-07-11 10:26:13 -0700390
391 @Override
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700392 public void onProviderDisabled(String provider) {
393 }
Nick Pellye0fd6932012-07-11 10:26:13 -0700394
395 @Override
396 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
397 String resultData, Bundle resultExtras) {
398 mWakeLock.release();
399 }
400
401 public void dump(PrintWriter pw) {
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700402 for (GeofenceState state : mFences) {
Soonil Nagarkar39766392020-03-28 13:59:26 -0700403 pw.println(state.mIdentity + " " + state.mFence);
Nick Pellye0fd6932012-07-11 10:26:13 -0700404 }
405 }
Victoria Lease4cd0a502012-11-02 16:24:08 -0700406
407 private final class GeofenceHandler extends Handler {
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700408 private GeofenceHandler(Looper looper) {
409 super(looper);
Victoria Lease4cd0a502012-11-02 16:24:08 -0700410 }
411
412 @Override
413 public void handleMessage(Message msg) {
Soonil Nagarkarb8466b72019-10-25 14:10:30 -0700414 if (msg.what == MSG_UPDATE_FENCES) {
415 updateFences();
Victoria Lease4cd0a502012-11-02 16:24:08 -0700416 }
417 }
418 }
Nick Pellye0fd6932012-07-11 10:26:13 -0700419}