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