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