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