blob: d3ceddd5682756f7a04271aa27e6497f3318e0af [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
2 * Copyright (C) 2020 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.geofence;
18
19import static android.location.LocationManager.FUSED_PROVIDER;
20import static android.location.LocationManager.KEY_PROXIMITY_ENTERING;
21
22import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
23
24import android.annotation.Nullable;
25import android.app.AppOpsManager;
26import android.app.PendingIntent;
27import android.content.Context;
28import android.content.Intent;
29import android.location.Geofence;
30import android.location.Location;
31import android.location.LocationListener;
32import android.location.LocationManager;
33import android.location.LocationRequest;
34import android.location.util.identity.CallerIdentity;
35import android.os.Binder;
36import android.os.PowerManager;
37import android.os.SystemClock;
38import android.os.WorkSource;
39import android.stats.location.LocationStatsEnums;
40import android.util.ArraySet;
41
42import com.android.internal.annotations.GuardedBy;
43import com.android.server.FgThread;
44import com.android.server.PendingIntentUtils;
45import com.android.server.location.LocationPermissions;
46import com.android.server.location.injector.Injector;
47import com.android.server.location.injector.LocationPermissionsHelper;
48import com.android.server.location.injector.LocationUsageLogger;
49import com.android.server.location.injector.SettingsHelper;
50import com.android.server.location.injector.UserInfoHelper;
51import com.android.server.location.injector.UserInfoHelper.UserListener;
52import com.android.server.location.listeners.ListenerMultiplexer;
53import com.android.server.location.listeners.PendingIntentListenerRegistration;
54
55import java.util.Collection;
56import java.util.Objects;
57
58/**
59 * Manages all geofences.
60 */
61public class GeofenceManager extends
62 ListenerMultiplexer<GeofenceManager.GeofenceKey, PendingIntent,
63 GeofenceManager.GeofenceRegistration, LocationRequest> implements
64 LocationListener {
65
66 private static final String TAG = "GeofenceManager";
67
68 private static final String ATTRIBUTION_TAG = "GeofencingService";
69
70 private static final long WAKELOCK_TIMEOUT_MS = 30000;
71
72 private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train)
73 private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
74 private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
75
76 // geofencing unfortunately allows multiple geofences under the same pending intent, even though
77 // this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
78 // geofence) instead of (pendingintent).
79 static class GeofenceKey {
80
81 private final PendingIntent mPendingIntent;
82 private final Geofence mGeofence;
83
84 GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
85 mPendingIntent = Objects.requireNonNull(pendingIntent);
86 mGeofence = Objects.requireNonNull(geofence);
87 }
88
89 public PendingIntent getPendingIntent() {
90 return mPendingIntent;
91 }
92
93 @Override
94 public boolean equals(Object o) {
95 if (o instanceof GeofenceKey) {
96 GeofenceKey that = (GeofenceKey) o;
97 return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
98 that.mGeofence);
99 }
100
101 return false;
102 }
103
104 @Override
105 public int hashCode() {
106 return mPendingIntent.hashCode();
107 }
108 }
109
110 protected class GeofenceRegistration extends
111 PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
112
113 private static final int STATE_UNKNOWN = 0;
114 private static final int STATE_INSIDE = 1;
115 private static final int STATE_OUTSIDE = 2;
116
117 private final Geofence mGeofence;
118 private final CallerIdentity mIdentity;
119 private final Location mCenter;
120 private final PowerManager.WakeLock mWakeLock;
121
122 private int mGeofenceState;
123
124 // we store these values because we don't trust the listeners not to give us dupes, not to
125 // spam us, and because checking the values may be more expensive
126 private boolean mPermitted;
127
128 @Nullable private Location mCachedLocation;
129 private float mCachedLocationDistanceM;
130
131 GeofenceRegistration(Geofence geofence, CallerIdentity identity,
132 PendingIntent pendingIntent) {
133 super(pendingIntent);
134
135 mGeofence = geofence;
136 mIdentity = identity;
137 mCenter = new Location("");
138 mCenter.setLatitude(geofence.getLatitude());
139 mCenter.setLongitude(geofence.getLongitude());
140
141 mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
142 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
143 TAG + ":" + identity.getPackageName());
144 mWakeLock.setReferenceCounted(true);
145 mWakeLock.setWorkSource(identity.addToWorkSource(null));
146 }
147
148 public Geofence getGeofence() {
149 return mGeofence;
150 }
151
152 public CallerIdentity getIdentity() {
153 return mIdentity;
154 }
155
156 @Override
157 public String getTag() {
158 return TAG;
159 }
160
161 @Override
162 protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
163 return geofenceKey.getPendingIntent();
164 }
165
166 @Override
167 protected GeofenceManager getOwner() {
168 return GeofenceManager.this;
169 }
170
171 @Override
172 protected void onRegister() {
173 super.onRegister();
174
175 mGeofenceState = STATE_UNKNOWN;
176 mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
177 mIdentity);
178 }
179
180 @Override
181 protected void onActive() {
182 Location location = getLastLocation();
183 if (location != null) {
184 executeOperation(onLocationChanged(location));
185 }
186 }
187
188 boolean isPermitted() {
189 return mPermitted;
190 }
191
192 boolean onLocationPermissionsChanged(@Nullable String packageName) {
193 if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
194 return onLocationPermissionsChanged();
195 }
196
197 return false;
198 }
199
200 boolean onLocationPermissionsChanged(int uid) {
201 if (mIdentity.getUid() == uid) {
202 return onLocationPermissionsChanged();
203 }
204
205 return false;
206 }
207
208 private boolean onLocationPermissionsChanged() {
209 boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
210 mIdentity);
211 if (permitted != mPermitted) {
212 mPermitted = permitted;
213 return true;
214 }
215
216 return false;
217 }
218
219 double getDistanceToBoundary(Location location) {
220 if (!location.equals(mCachedLocation)) {
221 mCachedLocation = location;
222 mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
223 }
224
225 return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
226 }
227
228 ListenerOperation<PendingIntent> onLocationChanged(Location location) {
229 // remove expired fences
230 if (mGeofence.isExpired()) {
231 remove();
232 return null;
233 }
234
235 mCachedLocation = location;
236 mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
237
238 int oldState = mGeofenceState;
239 float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
240 if (mCachedLocationDistanceM <= radius) {
241 mGeofenceState = STATE_INSIDE;
242 if (oldState != STATE_INSIDE) {
243 return pendingIntent -> sendIntent(pendingIntent, true);
244 }
245 } else {
246 mGeofenceState = STATE_OUTSIDE;
247 if (oldState == STATE_INSIDE) {
248 // return exit only if previously entered
249 return pendingIntent -> sendIntent(pendingIntent, false);
250 }
251 }
252
253 return null;
254 }
255
256 private void sendIntent(PendingIntent pendingIntent, boolean entering) {
257 Intent intent = new Intent().putExtra(KEY_PROXIMITY_ENTERING, entering);
258
259 mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
260 try {
261 // send() only enforces permissions for broadcast intents, but since clients can
262 // select any kind of pending intent we do not rely on send() to enforce permissions
263 pendingIntent.send(mContext, 0, intent, (pI, i, rC, rD, rE) -> mWakeLock.release(),
264 null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
265 } catch (PendingIntent.CanceledException e) {
266 mWakeLock.release();
267 removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
268 }
269 }
270
271 @Override
272 public String toString() {
273 StringBuilder builder = new StringBuilder();
274 builder.append(mIdentity);
275
276 ArraySet<String> flags = new ArraySet<>(1);
277 if (!mPermitted) {
278 flags.add("na");
279 }
280 if (!flags.isEmpty()) {
281 builder.append(" ").append(flags);
282 }
283
284 builder.append(" ").append(mGeofence);
285 return builder.toString();
286 }
287 }
288
289 final Object mLock = new Object();
290
291 protected final Context mContext;
292
293 private final UserListener mUserChangedListener = this::onUserChanged;
294 private final SettingsHelper.UserSettingChangedListener mLocationEnabledChangedListener =
295 this::onLocationEnabledChanged;
296 private final SettingsHelper.UserSettingChangedListener
297 mLocationPackageBlacklistChangedListener =
298 this::onLocationPackageBlacklistChanged;
299 private final LocationPermissionsHelper.LocationPermissionsListener
300 mLocationPermissionsListener =
301 new LocationPermissionsHelper.LocationPermissionsListener() {
302 @Override
303 public void onLocationPermissionsChanged(@Nullable String packageName) {
304 GeofenceManager.this.onLocationPermissionsChanged(packageName);
305 }
306
307 @Override
308 public void onLocationPermissionsChanged(int uid) {
309 GeofenceManager.this.onLocationPermissionsChanged(uid);
310 }
311 };
312
313 protected final UserInfoHelper mUserInfoHelper;
314 protected final LocationPermissionsHelper mLocationPermissionsHelper;
315 protected final SettingsHelper mSettingsHelper;
316 protected final LocationUsageLogger mLocationUsageLogger;
317
318 @GuardedBy("mLock")
319 @Nullable private LocationManager mLocationManager;
320
321 @GuardedBy("mLock")
322 @Nullable private Location mLastLocation;
323
324 public GeofenceManager(Context context, Injector injector) {
325 mContext = context.createAttributionContext(ATTRIBUTION_TAG);
326 mUserInfoHelper = injector.getUserInfoHelper();
327 mSettingsHelper = injector.getSettingsHelper();
328 mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
329 mLocationUsageLogger = injector.getLocationUsageLogger();
330 }
331
332 private LocationManager getLocationManager() {
333 synchronized (mLock) {
334 if (mLocationManager == null) {
335 mLocationManager = Objects.requireNonNull(
336 mContext.getSystemService(LocationManager.class));
337 }
338
339 return mLocationManager;
340 }
341 }
342
343 /**
344 * Adds a new geofence, replacing any geofence already associated with the PendingIntent. It
345 * doesn't make any real sense to register multiple geofences with the same pending intent, but
346 * we continue to allow this for backwards compatibility.
347 */
348 public void addGeofence(Geofence geofence, PendingIntent pendingIntent, String packageName,
349 @Nullable String attributionTag) {
350 LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_FINE);
351
352 CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName,
353 attributionTag, AppOpsManager.toReceiverId(pendingIntent));
354
355 final long ident = Binder.clearCallingIdentity();
356 try {
357 putRegistration(new GeofenceKey(pendingIntent, geofence),
358 new GeofenceRegistration(geofence, identity, pendingIntent));
359 } finally {
360 Binder.restoreCallingIdentity(ident);
361 }
362 }
363
364 /**
365 * Removes the geofence associated with the PendingIntent.
366 */
367 public void removeGeofence(PendingIntent pendingIntent) {
368 final long identity = Binder.clearCallingIdentity();
369 try {
370 removeRegistrationIf(key -> key.getPendingIntent().equals(pendingIntent));
371 } finally {
372 Binder.restoreCallingIdentity(identity);
373 }
374 }
375
376 @Override
377 protected boolean isActive(GeofenceRegistration registration) {
378 return registration.isPermitted() && isActive(registration.getIdentity());
379 }
380
381 private boolean isActive(CallerIdentity identity) {
382 if (identity.isSystemServer()) {
383 if (!mSettingsHelper.isLocationEnabled(mUserInfoHelper.getCurrentUserId())) {
384 return false;
385 }
386 } else {
387 if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
388 return false;
389 }
390 if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
391 return false;
392 }
393 if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
394 identity.getPackageName())) {
395 return false;
396 }
397 }
398
399 return true;
400 }
401
402 @Override
403 protected void onRegister() {
404 mUserInfoHelper.addListener(mUserChangedListener);
405 mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
406 mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
407 mLocationPackageBlacklistChangedListener);
408 mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
409 }
410
411 @Override
412 protected void onUnregister() {
413 mUserInfoHelper.removeListener(mUserChangedListener);
414 mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
415 mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
416 mLocationPackageBlacklistChangedListener);
417 mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
418 }
419
420 @Override
421 protected void onRegistrationAdded(GeofenceKey key, GeofenceRegistration registration) {
422 mLocationUsageLogger.logLocationApiUsage(
423 LocationStatsEnums.USAGE_ENDED,
424 LocationStatsEnums.API_REQUEST_GEOFENCE,
425 registration.getIdentity().getPackageName(),
426 registration.getIdentity().getAttributionTag(),
427 null,
428 /* LocationRequest= */ null,
429 /* hasListener= */ false,
430 true,
431 registration.getGeofence(), true);
432 }
433
434 @Override
435 protected void onRegistrationRemoved(GeofenceKey key, GeofenceRegistration registration) {
436 mLocationUsageLogger.logLocationApiUsage(
437 LocationStatsEnums.USAGE_ENDED,
438 LocationStatsEnums.API_REQUEST_GEOFENCE,
439 registration.getIdentity().getPackageName(),
440 registration.getIdentity().getAttributionTag(),
441 null,
442 /* LocationRequest= */ null,
443 /* hasListener= */ false,
444 true,
445 registration.getGeofence(), true);
446 }
447
448 @Override
449 protected boolean registerWithService(LocationRequest locationRequest,
450 Collection<GeofenceRegistration> registrations) {
451 getLocationManager().requestLocationUpdates(FUSED_PROVIDER, locationRequest,
452 FgThread.getExecutor(), this);
453 return true;
454 }
455
456 @Override
457 protected void unregisterWithService() {
458 synchronized (mLock) {
459 getLocationManager().removeUpdates(this);
460 mLastLocation = null;
461 }
462 }
463
464 @Override
465 protected LocationRequest mergeRegistrations(Collection<GeofenceRegistration> registrations) {
466 Location location = getLastLocation();
467
468 long realtimeMs = SystemClock.elapsedRealtime();
469
470 WorkSource workSource = null;
471 double minFenceDistanceM = Double.MAX_VALUE;
472 for (GeofenceRegistration registration : registrations) {
473 if (registration.getGeofence().isExpired(realtimeMs)) {
474 continue;
475 }
476
477 workSource = registration.getIdentity().addToWorkSource(workSource);
478
479 if (location != null) {
480 double fenceDistanceM = registration.getDistanceToBoundary(location);
481 if (fenceDistanceM < minFenceDistanceM) {
482 minFenceDistanceM = fenceDistanceM;
483 }
484 }
485 }
486
487 long intervalMs;
488 if (Double.compare(minFenceDistanceM, Double.MAX_VALUE) < 0) {
489 intervalMs = (long) Math.min(MAX_LOCATION_INTERVAL_MS,
490 Math.max(
491 mSettingsHelper.getBackgroundThrottleProximityAlertIntervalMs(),
492 minFenceDistanceM * 1000 / MAX_SPEED_M_S));
493 } else {
494 intervalMs = mSettingsHelper.getBackgroundThrottleProximityAlertIntervalMs();
495 }
496
497 return new LocationRequest.Builder(intervalMs)
498 .setMinUpdateIntervalMillis(0)
499 .setHiddenFromAppOps(true)
500 .setWorkSource(workSource)
501 .build();
502 }
503
504
505 @Override
506 public void onLocationChanged(Location location) {
507 synchronized (mLock) {
508 mLastLocation = location;
509 }
510
511 deliverToListeners(registration -> {
512 return registration.onLocationChanged(location);
513 });
514 updateService();
515 }
516
517 @Nullable Location getLastLocation() {
518 Location location;
519 synchronized (mLock) {
520 location = mLastLocation;
521 }
522
523 if (location == null) {
524 location = getLocationManager().getLastLocation();
525 }
526
527 if (location != null) {
528 if (location.getElapsedRealtimeAgeMillis() > MAX_LOCATION_AGE_MS) {
529 location = null;
530 }
531 }
532
533 return location;
534 }
535
536 void onUserChanged(int userId, int change) {
537 // current user changes affect whether system server location requests are allowed to access
538 // location, and visibility changes affect whether any given user may access location.
539 if (change == UserListener.CURRENT_USER_CHANGED
540 || change == UserListener.USER_VISIBILITY_CHANGED) {
541 updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
542 }
543 }
544
545 void onLocationEnabledChanged(int userId) {
546 updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
547 }
548
549 void onLocationPackageBlacklistChanged(int userId) {
550 updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
551 }
552
553 void onLocationPermissionsChanged(@Nullable String packageName) {
554 updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
555 }
556
557 void onLocationPermissionsChanged(int uid) {
558 updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
559 }
560}