1 /* 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 17 package com.android.server.location.geofence; 18 19 import static android.location.LocationManager.FUSED_PROVIDER; 20 import static android.location.LocationManager.KEY_PROXIMITY_ENTERING; 21 22 import static com.android.server.location.LocationPermissions.PERMISSION_FINE; 23 24 import android.annotation.Nullable; 25 import android.app.AppOpsManager; 26 import android.app.PendingIntent; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.location.Geofence; 30 import android.location.Location; 31 import android.location.LocationListener; 32 import android.location.LocationManager; 33 import android.location.LocationRequest; 34 import android.location.util.identity.CallerIdentity; 35 import android.os.Binder; 36 import android.os.PowerManager; 37 import android.os.SystemClock; 38 import android.os.WorkSource; 39 import android.stats.location.LocationStatsEnums; 40 import android.util.ArraySet; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.server.FgThread; 44 import com.android.server.PendingIntentUtils; 45 import com.android.server.location.LocationPermissions; 46 import com.android.server.location.injector.Injector; 47 import com.android.server.location.injector.LocationPermissionsHelper; 48 import com.android.server.location.injector.LocationUsageLogger; 49 import com.android.server.location.injector.SettingsHelper; 50 import com.android.server.location.injector.UserInfoHelper; 51 import com.android.server.location.injector.UserInfoHelper.UserListener; 52 import com.android.server.location.listeners.ListenerMultiplexer; 53 import com.android.server.location.listeners.PendingIntentListenerRegistration; 54 55 import java.util.Collection; 56 import java.util.Objects; 57 58 /** 59 * Manages all geofences. 60 */ 61 public 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 GeofenceKey(PendingIntent pendingIntent, Geofence geofence)84 GeofenceKey(PendingIntent pendingIntent, Geofence geofence) { 85 mPendingIntent = Objects.requireNonNull(pendingIntent); 86 mGeofence = Objects.requireNonNull(geofence); 87 } 88 getPendingIntent()89 public PendingIntent getPendingIntent() { 90 return mPendingIntent; 91 } 92 93 @Override equals(Object o)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 hashCode()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 GeofenceRegistration(Geofence geofence, CallerIdentity identity, PendingIntent pendingIntent)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 getGeofence()148 public Geofence getGeofence() { 149 return mGeofence; 150 } 151 getIdentity()152 public CallerIdentity getIdentity() { 153 return mIdentity; 154 } 155 156 @Override getTag()157 public String getTag() { 158 return TAG; 159 } 160 161 @Override getPendingIntentFromKey(GeofenceKey geofenceKey)162 protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) { 163 return geofenceKey.getPendingIntent(); 164 } 165 166 @Override getOwner()167 protected GeofenceManager getOwner() { 168 return GeofenceManager.this; 169 } 170 171 @Override onRegister()172 protected void onRegister() { 173 super.onRegister(); 174 175 mGeofenceState = STATE_UNKNOWN; 176 mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, 177 mIdentity); 178 } 179 180 @Override onActive()181 protected void onActive() { 182 Location location = getLastLocation(); 183 if (location != null) { 184 executeOperation(onLocationChanged(location)); 185 } 186 } 187 isPermitted()188 boolean isPermitted() { 189 return mPermitted; 190 } 191 onLocationPermissionsChanged(@ullable String packageName)192 boolean onLocationPermissionsChanged(@Nullable String packageName) { 193 if (packageName == null || mIdentity.getPackageName().equals(packageName)) { 194 return onLocationPermissionsChanged(); 195 } 196 197 return false; 198 } 199 onLocationPermissionsChanged(int uid)200 boolean onLocationPermissionsChanged(int uid) { 201 if (mIdentity.getUid() == uid) { 202 return onLocationPermissionsChanged(); 203 } 204 205 return false; 206 } 207 onLocationPermissionsChanged()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 getDistanceToBoundary(Location location)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 onLocationChanged(Location location)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 sendIntent(PendingIntent pendingIntent, boolean entering)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 toString()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 GeofenceManager(Context context, Injector injector)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 getLocationManager()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 */ addGeofence(Geofence geofence, PendingIntent pendingIntent, String packageName, @Nullable String attributionTag)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 */ removeGeofence(PendingIntent pendingIntent)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 isActive(GeofenceRegistration registration)377 protected boolean isActive(GeofenceRegistration registration) { 378 return registration.isPermitted() && isActive(registration.getIdentity()); 379 } 380 isActive(CallerIdentity identity)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 onRegister()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 onUnregister()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 onRegistrationAdded(GeofenceKey key, GeofenceRegistration registration)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 onRegistrationRemoved(GeofenceKey key, GeofenceRegistration registration)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 registerWithService(LocationRequest locationRequest, Collection<GeofenceRegistration> registrations)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 unregisterWithService()457 protected void unregisterWithService() { 458 synchronized (mLock) { 459 getLocationManager().removeUpdates(this); 460 mLastLocation = null; 461 } 462 } 463 464 @Override mergeRegistrations(Collection<GeofenceRegistration> registrations)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 onLocationChanged(Location location)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 getLastLocation()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 onUserChanged(int userId, int change)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 onLocationEnabledChanged(int userId)545 void onLocationEnabledChanged(int userId) { 546 updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); 547 } 548 onLocationPackageBlacklistChanged(int userId)549 void onLocationPackageBlacklistChanged(int userId) { 550 updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); 551 } 552 onLocationPermissionsChanged(@ullable String packageName)553 void onLocationPermissionsChanged(@Nullable String packageName) { 554 updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); 555 } 556 onLocationPermissionsChanged(int uid)557 void onLocationPermissionsChanged(int uid) { 558 updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); 559 } 560 } 561