1 /* 2 * Copyright (C) 2012 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; 18 19 import java.io.PrintWriter; 20 import java.util.Iterator; 21 import java.util.LinkedList; 22 import java.util.List; 23 24 import android.app.AppOpsManager; 25 import android.app.PendingIntent; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.database.ContentObserver; 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.os.Bundle; 36 import android.os.Handler; 37 import android.os.Message; 38 import android.os.PowerManager; 39 import android.os.SystemClock; 40 import android.os.UserHandle; 41 import android.provider.Settings; 42 import android.util.Slog; 43 44 import com.android.server.LocationManagerService; 45 import com.android.server.PendingIntentUtils; 46 47 public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { 48 private static final String TAG = "GeofenceManager"; 49 private static final boolean D = LocationManagerService.D; 50 51 private static final int MSG_UPDATE_FENCES = 1; 52 53 /** 54 * Assume a maximum land speed, as a heuristic to throttle location updates. 55 * (Air travel should result in an airplane mode toggle which will 56 * force a new location update anyway). 57 */ 58 private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) 59 60 /** 61 * Maximum age after which a location is no longer considered fresh enough to use. 62 */ 63 private static final long MAX_AGE_NANOS = 5 * 60 * 1000000000L; // five minutes 64 65 /** 66 * The default value of most frequent update interval allowed. 67 */ 68 private static final long DEFAULT_MIN_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes 69 70 /** 71 * Least frequent update interval allowed. 72 */ 73 private static final long MAX_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours 74 75 private final Context mContext; 76 private final LocationManager mLocationManager; 77 private final AppOpsManager mAppOps; 78 private final PowerManager.WakeLock mWakeLock; 79 private final GeofenceHandler mHandler; 80 private final LocationBlacklist mBlacklist; 81 82 private Object mLock = new Object(); 83 84 // access to members below is synchronized on mLock 85 /** 86 * A list containing all registered geofences. 87 */ 88 private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); 89 90 /** 91 * This is set true when we have an active request for {@link Location} updates via 92 * {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener, 93 * android.os.Looper). 94 */ 95 private boolean mReceivingLocationUpdates; 96 97 /** 98 * The update interval component of the current active {@link Location} update request. 99 */ 100 private long mLocationUpdateInterval; 101 102 /** 103 * The {@link Location} most recently received via {@link #onLocationChanged(Location)}. 104 */ 105 private Location mLastLocationUpdate; 106 107 /** 108 * This is set true when a {@link Location} is received via 109 * {@link #onLocationChanged(Location)} or {@link #scheduleUpdateFencesLocked()}, and cleared 110 * when that Location has been processed via {@link #updateFences()} 111 */ 112 private boolean mPendingUpdate; 113 114 /** 115 * The actual value of most frequent update interval allowed. 116 */ 117 private long mEffectiveMinIntervalMs; 118 private ContentResolver mResolver; 119 GeofenceManager(Context context, LocationBlacklist blacklist)120 public GeofenceManager(Context context, LocationBlacklist blacklist) { 121 mContext = context; 122 mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); 123 mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); 124 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 125 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 126 mHandler = new GeofenceHandler(); 127 mBlacklist = blacklist; 128 mResolver = mContext.getContentResolver(); 129 updateMinInterval(); 130 mResolver.registerContentObserver( 131 Settings.Global.getUriFor( 132 Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS), 133 true, 134 new ContentObserver(mHandler) { 135 @Override 136 public void onChange(boolean selfChange) { 137 synchronized (mLock) { 138 updateMinInterval(); 139 } 140 } 141 }, UserHandle.USER_ALL); 142 } 143 144 /** 145 * Updates the minimal location request frequency. 146 */ updateMinInterval()147 private void updateMinInterval() { 148 mEffectiveMinIntervalMs = Settings.Global.getLong(mResolver, 149 Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS, 150 DEFAULT_MIN_INTERVAL_MS); 151 } 152 addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int allowedResolutionLevel, int uid, String packageName)153 public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, 154 int allowedResolutionLevel, int uid, String packageName) { 155 if (D) { 156 Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence 157 + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName); 158 } 159 160 GeofenceState state = new GeofenceState(geofence, 161 request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent); 162 synchronized (mLock) { 163 // first make sure it doesn't already exist 164 for (int i = mFences.size() - 1; i >= 0; i--) { 165 GeofenceState w = mFences.get(i); 166 if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) { 167 // already exists, remove the old one 168 mFences.remove(i); 169 break; 170 } 171 } 172 mFences.add(state); 173 scheduleUpdateFencesLocked(); 174 } 175 } 176 removeFence(Geofence fence, PendingIntent intent)177 public void removeFence(Geofence fence, PendingIntent intent) { 178 if (D) { 179 Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent); 180 } 181 182 synchronized (mLock) { 183 Iterator<GeofenceState> iter = mFences.iterator(); 184 while (iter.hasNext()) { 185 GeofenceState state = iter.next(); 186 if (state.mIntent.equals(intent)) { 187 188 if (fence == null) { 189 // always remove 190 iter.remove(); 191 } else { 192 // just remove matching fences 193 if (fence.equals(state.mFence)) { 194 iter.remove(); 195 } 196 } 197 } 198 } 199 scheduleUpdateFencesLocked(); 200 } 201 } 202 removeFence(String packageName)203 public void removeFence(String packageName) { 204 if (D) { 205 Slog.d(TAG, "removeFence: packageName=" + packageName); 206 } 207 208 synchronized (mLock) { 209 Iterator<GeofenceState> iter = mFences.iterator(); 210 while (iter.hasNext()) { 211 GeofenceState state = iter.next(); 212 if (state.mPackageName.equals(packageName)) { 213 iter.remove(); 214 } 215 } 216 scheduleUpdateFencesLocked(); 217 } 218 } 219 removeExpiredFencesLocked()220 private void removeExpiredFencesLocked() { 221 long time = SystemClock.elapsedRealtime(); 222 Iterator<GeofenceState> iter = mFences.iterator(); 223 while (iter.hasNext()) { 224 GeofenceState state = iter.next(); 225 if (state.mExpireAt < time) { 226 iter.remove(); 227 } 228 } 229 } 230 scheduleUpdateFencesLocked()231 private void scheduleUpdateFencesLocked() { 232 if (!mPendingUpdate) { 233 mPendingUpdate = true; 234 mHandler.sendEmptyMessage(MSG_UPDATE_FENCES); 235 } 236 } 237 238 /** 239 * Returns the location received most recently from {@link #onLocationChanged(Location)}, 240 * or consult {@link LocationManager#getLastLocation()} if none has arrived. Does not return 241 * either if the location would be too stale to be useful. 242 * 243 * @return a fresh, valid Location, or null if none is available 244 */ getFreshLocationLocked()245 private Location getFreshLocationLocked() { 246 // Prefer mLastLocationUpdate to LocationManager.getLastLocation(). 247 Location location = mReceivingLocationUpdates ? mLastLocationUpdate : null; 248 if (location == null && !mFences.isEmpty()) { 249 location = mLocationManager.getLastLocation(); 250 } 251 252 // Early out for null location. 253 if (location == null) { 254 return null; 255 } 256 257 // Early out for stale location. 258 long now = SystemClock.elapsedRealtimeNanos(); 259 if (now - location.getElapsedRealtimeNanos() > MAX_AGE_NANOS) { 260 return null; 261 } 262 263 // Made it this far? Return our fresh, valid location. 264 return location; 265 } 266 267 /** 268 * The geofence update loop. This function removes expired fences, then tests the most 269 * recently-received {@link Location} against each registered {@link GeofenceState}, sending 270 * {@link Intent}s for geofences that have been tripped. It also adjusts the active location 271 * update request with {@link LocationManager} as appropriate for any active geofences. 272 */ 273 // Runs on the handler. updateFences()274 private void updateFences() { 275 List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); 276 List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); 277 278 synchronized (mLock) { 279 mPendingUpdate = false; 280 281 // Remove expired fences. 282 removeExpiredFencesLocked(); 283 284 // Get a location to work with, either received via onLocationChanged() or 285 // via LocationManager.getLastLocation(). 286 Location location = getFreshLocationLocked(); 287 288 // Update all fences. 289 // Keep track of the distance to the nearest fence. 290 double minFenceDistance = Double.MAX_VALUE; 291 boolean needUpdates = false; 292 for (GeofenceState state : mFences) { 293 if (mBlacklist.isBlacklisted(state.mPackageName)) { 294 if (D) { 295 Slog.d(TAG, "skipping geofence processing for blacklisted app: " 296 + state.mPackageName); 297 } 298 continue; 299 } 300 301 int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel); 302 if (op >= 0) { 303 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid, 304 state.mPackageName) != AppOpsManager.MODE_ALLOWED) { 305 if (D) { 306 Slog.d(TAG, "skipping geofence processing for no op app: " 307 + state.mPackageName); 308 } 309 continue; 310 } 311 } 312 313 needUpdates = true; 314 if (location != null) { 315 int event = state.processLocation(location); 316 if ((event & GeofenceState.FLAG_ENTER) != 0) { 317 enterIntents.add(state.mIntent); 318 } 319 if ((event & GeofenceState.FLAG_EXIT) != 0) { 320 exitIntents.add(state.mIntent); 321 } 322 323 // FIXME: Ideally this code should take into account the accuracy of the 324 // location fix that was used to calculate the distance in the first place. 325 double fenceDistance = state.getDistanceToBoundary(); // MAX_VALUE if unknown 326 if (fenceDistance < minFenceDistance) { 327 minFenceDistance = fenceDistance; 328 } 329 } 330 } 331 332 // Request or cancel location updates if needed. 333 if (needUpdates) { 334 // Request location updates. 335 // Compute a location update interval based on the distance to the nearest fence. 336 long intervalMs; 337 if (location != null && Double.compare(minFenceDistance, Double.MAX_VALUE) != 0) { 338 intervalMs = (long)Math.min(MAX_INTERVAL_MS, Math.max(mEffectiveMinIntervalMs, 339 minFenceDistance * 1000 / MAX_SPEED_M_S)); 340 } else { 341 intervalMs = mEffectiveMinIntervalMs; 342 } 343 if (!mReceivingLocationUpdates || mLocationUpdateInterval != intervalMs) { 344 mReceivingLocationUpdates = true; 345 mLocationUpdateInterval = intervalMs; 346 mLastLocationUpdate = location; 347 348 LocationRequest request = new LocationRequest(); 349 request.setInterval(intervalMs).setFastestInterval(0); 350 mLocationManager.requestLocationUpdates(request, this, mHandler.getLooper()); 351 } 352 } else { 353 // Cancel location updates. 354 if (mReceivingLocationUpdates) { 355 mReceivingLocationUpdates = false; 356 mLocationUpdateInterval = 0; 357 mLastLocationUpdate = null; 358 359 mLocationManager.removeUpdates(this); 360 } 361 } 362 363 if (D) { 364 Slog.d(TAG, "updateFences: location=" + location 365 + ", mFences.size()=" + mFences.size() 366 + ", mReceivingLocationUpdates=" + mReceivingLocationUpdates 367 + ", mLocationUpdateInterval=" + mLocationUpdateInterval 368 + ", mLastLocationUpdate=" + mLastLocationUpdate); 369 } 370 } 371 372 // release lock before sending intents 373 for (PendingIntent intent : exitIntents) { 374 sendIntentExit(intent); 375 } 376 for (PendingIntent intent : enterIntents) { 377 sendIntentEnter(intent); 378 } 379 } 380 sendIntentEnter(PendingIntent pendingIntent)381 private void sendIntentEnter(PendingIntent pendingIntent) { 382 if (D) { 383 Slog.d(TAG, "sendIntentEnter: pendingIntent=" + pendingIntent); 384 } 385 386 Intent intent = new Intent(); 387 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); 388 sendIntent(pendingIntent, intent); 389 } 390 sendIntentExit(PendingIntent pendingIntent)391 private void sendIntentExit(PendingIntent pendingIntent) { 392 if (D) { 393 Slog.d(TAG, "sendIntentExit: pendingIntent=" + pendingIntent); 394 } 395 396 Intent intent = new Intent(); 397 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); 398 sendIntent(pendingIntent, intent); 399 } 400 sendIntent(PendingIntent pendingIntent, Intent intent)401 private void sendIntent(PendingIntent pendingIntent, Intent intent) { 402 mWakeLock.acquire(); 403 try { 404 pendingIntent.send(mContext, 0, intent, this, null, 405 android.Manifest.permission.ACCESS_FINE_LOCATION, 406 PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); 407 } catch (PendingIntent.CanceledException e) { 408 removeFence(null, pendingIntent); 409 mWakeLock.release(); 410 } 411 // ...otherwise, mWakeLock.release() gets called by onSendFinished() 412 } 413 414 // Runs on the handler (which was passed into LocationManager.requestLocationUpdates()) 415 @Override onLocationChanged(Location location)416 public void onLocationChanged(Location location) { 417 synchronized (mLock) { 418 if (mReceivingLocationUpdates) { 419 mLastLocationUpdate = location; 420 } 421 422 // Update the fences immediately before returning in 423 // case the caller is holding a wakelock. 424 if (mPendingUpdate) { 425 mHandler.removeMessages(MSG_UPDATE_FENCES); 426 } else { 427 mPendingUpdate = true; 428 } 429 } 430 updateFences(); 431 } 432 433 @Override onStatusChanged(String provider, int status, Bundle extras)434 public void onStatusChanged(String provider, int status, Bundle extras) { } 435 436 @Override onProviderEnabled(String provider)437 public void onProviderEnabled(String provider) { } 438 439 @Override onProviderDisabled(String provider)440 public void onProviderDisabled(String provider) { } 441 442 @Override onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, String resultData, Bundle resultExtras)443 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, 444 String resultData, Bundle resultExtras) { 445 mWakeLock.release(); 446 } 447 dump(PrintWriter pw)448 public void dump(PrintWriter pw) { 449 pw.println(" Geofences:"); 450 451 for (GeofenceState state : mFences) { 452 pw.append(" "); 453 pw.append(state.mPackageName); 454 pw.append(" "); 455 pw.append(state.mFence.toString()); 456 pw.append("\n"); 457 } 458 } 459 460 private final class GeofenceHandler extends Handler { GeofenceHandler()461 public GeofenceHandler() { 462 super(true /*async*/); 463 } 464 465 @Override handleMessage(Message msg)466 public void handleMessage(Message msg) { 467 switch (msg.what) { 468 case MSG_UPDATE_FENCES: { 469 updateFences(); 470 break; 471 } 472 } 473 } 474 } 475 } 476