Home | History | Annotate | Download | only in location
      1 /*
      2  * Copyright (C) 2013 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 android.hardware.location;
     18 
     19 import android.content.Context;
     20 import android.content.pm.PackageManager;
     21 import android.location.IFusedGeofenceHardware;
     22 import android.location.IGpsGeofenceHardware;
     23 import android.location.Location;
     24 import android.os.Handler;
     25 import android.os.IBinder;
     26 import android.os.IInterface;
     27 import android.os.Message;
     28 import android.os.PowerManager;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 import android.util.SparseArray;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Iterator;
     35 
     36 /**
     37  * This class manages the geofences which are handled by hardware.
     38  *
     39  * @hide
     40  */
     41 public final class GeofenceHardwareImpl {
     42     private static final String TAG = "GeofenceHardwareImpl";
     43     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     44     private static final int FIRST_VERSION_WITH_CAPABILITIES = 2;
     45 
     46     private final Context mContext;
     47     private static GeofenceHardwareImpl sInstance;
     48     private PowerManager.WakeLock mWakeLock;
     49     private final SparseArray<IGeofenceHardwareCallback> mGeofences =
     50             new SparseArray<IGeofenceHardwareCallback>();
     51     private final ArrayList<IGeofenceHardwareMonitorCallback>[] mCallbacks =
     52             new ArrayList[GeofenceHardware.NUM_MONITORS];
     53     private final ArrayList<Reaper> mReapers = new ArrayList<Reaper>();
     54 
     55     private IFusedGeofenceHardware mFusedService;
     56     private IGpsGeofenceHardware mGpsService;
     57     private int mCapabilities;
     58     private int mVersion = 1;
     59 
     60     private int[] mSupportedMonitorTypes = new int[GeofenceHardware.NUM_MONITORS];
     61 
     62     // mGeofenceHandler message types
     63     private static final int GEOFENCE_TRANSITION_CALLBACK = 1;
     64     private static final int ADD_GEOFENCE_CALLBACK = 2;
     65     private static final int REMOVE_GEOFENCE_CALLBACK = 3;
     66     private static final int PAUSE_GEOFENCE_CALLBACK = 4;
     67     private static final int RESUME_GEOFENCE_CALLBACK = 5;
     68     private static final int GEOFENCE_CALLBACK_BINDER_DIED = 6;
     69 
     70     // mCallbacksHandler message types
     71     private static final int GEOFENCE_STATUS = 1;
     72     private static final int CALLBACK_ADD = 2;
     73     private static final int CALLBACK_REMOVE = 3;
     74     private static final int MONITOR_CALLBACK_BINDER_DIED = 4;
     75 
     76     // mReaperHandler message types
     77     private static final int REAPER_GEOFENCE_ADDED = 1;
     78     private static final int REAPER_MONITOR_CALLBACK_ADDED = 2;
     79     private static final int REAPER_REMOVED = 3;
     80 
     81     // The following constants need to match GpsLocationFlags enum in gps.h
     82     private static final int LOCATION_INVALID = 0;
     83     private static final int LOCATION_HAS_LAT_LONG = 1;
     84     private static final int LOCATION_HAS_ALTITUDE = 2;
     85     private static final int LOCATION_HAS_SPEED = 4;
     86     private static final int LOCATION_HAS_BEARING = 8;
     87     private static final int LOCATION_HAS_ACCURACY = 16;
     88 
     89     // Resolution level constants used for permission checks.
     90     // These constants must be in increasing order of finer resolution.
     91     private static final int RESOLUTION_LEVEL_NONE = 1;
     92     private static final int RESOLUTION_LEVEL_COARSE = 2;
     93     private static final int RESOLUTION_LEVEL_FINE = 3;
     94 
     95     // Capability constant corresponding to fused_location.h entry when geofencing supports GNNS.
     96     private static final int CAPABILITY_GNSS = 1;
     97 
     98     public synchronized static GeofenceHardwareImpl getInstance(Context context) {
     99         if (sInstance == null) {
    100             sInstance = new GeofenceHardwareImpl(context);
    101         }
    102         return sInstance;
    103     }
    104 
    105     private GeofenceHardwareImpl(Context context) {
    106         mContext = context;
    107         // Init everything to unsupported.
    108         setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
    109                 GeofenceHardware.MONITOR_UNSUPPORTED);
    110         setMonitorAvailability(
    111                 GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
    112                 GeofenceHardware.MONITOR_UNSUPPORTED);
    113 
    114     }
    115 
    116     private void acquireWakeLock() {
    117         if (mWakeLock == null) {
    118             PowerManager powerManager =
    119                     (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    120             mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    121         }
    122         mWakeLock.acquire();
    123     }
    124 
    125     private void releaseWakeLock() {
    126         if (mWakeLock.isHeld()) mWakeLock.release();
    127     }
    128 
    129     private void updateGpsHardwareAvailability() {
    130         //Check which monitors are available.
    131         boolean gpsSupported;
    132         try {
    133             gpsSupported = mGpsService.isHardwareGeofenceSupported();
    134         } catch (RemoteException e) {
    135             Log.e(TAG, "Remote Exception calling LocationManagerService");
    136             gpsSupported = false;
    137         }
    138 
    139         if (gpsSupported) {
    140             // Its assumed currently available at startup.
    141             // native layer will update later.
    142             setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
    143                     GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE);
    144         }
    145     }
    146 
    147     private void updateFusedHardwareAvailability() {
    148         boolean fusedSupported;
    149         try {
    150             final boolean hasGnnsCapabilities = (mVersion < FIRST_VERSION_WITH_CAPABILITIES)
    151                     || (mCapabilities & CAPABILITY_GNSS) != 0;
    152             fusedSupported = (mFusedService != null
    153                     ? mFusedService.isSupported() && hasGnnsCapabilities
    154                     : false);
    155         } catch (RemoteException e) {
    156             Log.e(TAG, "RemoteException calling LocationManagerService");
    157             fusedSupported = false;
    158         }
    159 
    160         if(fusedSupported) {
    161             setMonitorAvailability(
    162                     GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
    163                     GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE);
    164         }
    165     }
    166 
    167     public void setGpsHardwareGeofence(IGpsGeofenceHardware service) {
    168         if (mGpsService == null) {
    169             mGpsService = service;
    170             updateGpsHardwareAvailability();
    171         } else if (service == null) {
    172             mGpsService = null;
    173             Log.w(TAG, "GPS Geofence Hardware service seems to have crashed");
    174         } else {
    175             Log.e(TAG, "Error: GpsService being set again.");
    176         }
    177     }
    178 
    179     public void onCapabilities(int capabilities) {
    180         mCapabilities = capabilities;
    181         updateFusedHardwareAvailability();
    182     }
    183 
    184     public void setVersion(int version) {
    185         mVersion = version;
    186         updateFusedHardwareAvailability();
    187     }
    188 
    189     public void setFusedGeofenceHardware(IFusedGeofenceHardware service) {
    190         if(mFusedService == null) {
    191             mFusedService = service;
    192             updateFusedHardwareAvailability();
    193         } else if(service == null) {
    194             mFusedService = null;
    195             Log.w(TAG, "Fused Geofence Hardware service seems to have crashed");
    196         } else {
    197             Log.e(TAG, "Error: FusedService being set again");
    198         }
    199     }
    200 
    201     public int[] getMonitoringTypes() {
    202         boolean gpsSupported;
    203         boolean fusedSupported;
    204         synchronized (mSupportedMonitorTypes) {
    205             gpsSupported = mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE]
    206                     != GeofenceHardware.MONITOR_UNSUPPORTED;
    207             fusedSupported = mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE]
    208                     != GeofenceHardware.MONITOR_UNSUPPORTED;
    209         }
    210 
    211         if(gpsSupported) {
    212             if(fusedSupported) {
    213                 return new int[] {
    214                         GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
    215                         GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE };
    216             } else {
    217                 return new int[] { GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE };
    218             }
    219         } else if (fusedSupported) {
    220             return new int[] { GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE };
    221         } else {
    222             return new int[0];
    223         }
    224     }
    225 
    226     public int getStatusOfMonitoringType(int monitoringType) {
    227         synchronized (mSupportedMonitorTypes) {
    228             if (monitoringType >= mSupportedMonitorTypes.length || monitoringType < 0) {
    229                 throw new IllegalArgumentException("Unknown monitoring type");
    230             }
    231             return mSupportedMonitorTypes[monitoringType];
    232         }
    233     }
    234 
    235     public int getCapabilitiesForMonitoringType(int monitoringType) {
    236         switch (mSupportedMonitorTypes[monitoringType]) {
    237             case GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE:
    238                 switch (monitoringType) {
    239                     case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
    240                         return CAPABILITY_GNSS;
    241                     case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
    242                         if (mVersion >= FIRST_VERSION_WITH_CAPABILITIES) {
    243                             return mCapabilities;
    244                         }
    245                         // This was the implied capability on old FLP HAL versions that didn't
    246                         // have the capability callback.
    247                         return CAPABILITY_GNSS;
    248                 }
    249                 break;
    250         }
    251         return 0;
    252     }
    253 
    254     public boolean addCircularFence(
    255             int monitoringType,
    256             GeofenceHardwareRequestParcelable request,
    257             IGeofenceHardwareCallback callback) {
    258         int geofenceId = request.getId();
    259 
    260         // This API is not thread safe. Operations on the same geofence need to be serialized
    261         // by upper layers
    262         if (DEBUG) {
    263             String message = String.format(
    264                     "addCircularFence: monitoringType=%d, %s",
    265                     monitoringType,
    266                     request);
    267             Log.d(TAG, message);
    268         }
    269         boolean result;
    270 
    271         // The callback must be added before addCircularHardwareGeofence is called otherwise the
    272         // callback might not be called after the geofence is added in the geofence hardware.
    273         // This also means that the callback must be removed if the addCircularHardwareGeofence
    274         // operations is not called or fails.
    275         synchronized (mGeofences) {
    276             mGeofences.put(geofenceId, callback);
    277         }
    278 
    279         switch (monitoringType) {
    280             case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
    281                 if (mGpsService == null) return false;
    282                 try {
    283                     result = mGpsService.addCircularHardwareGeofence(
    284                             request.getId(),
    285                             request.getLatitude(),
    286                             request.getLongitude(),
    287                             request.getRadius(),
    288                             request.getLastTransition(),
    289                             request.getMonitorTransitions(),
    290                             request.getNotificationResponsiveness(),
    291                             request.getUnknownTimer());
    292                 } catch (RemoteException e) {
    293                     Log.e(TAG, "AddGeofence: Remote Exception calling LocationManagerService");
    294                     result = false;
    295                 }
    296                 break;
    297             case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
    298                 if(mFusedService == null) {
    299                     return false;
    300                 }
    301                 try {
    302                     mFusedService.addGeofences(
    303                             new GeofenceHardwareRequestParcelable[] { request });
    304                     result = true;
    305                 } catch(RemoteException e) {
    306                     Log.e(TAG, "AddGeofence: RemoteException calling LocationManagerService");
    307                     result = false;
    308                 }
    309                 break;
    310             default:
    311                 result = false;
    312         }
    313         if (result) {
    314             Message m = mReaperHandler.obtainMessage(REAPER_GEOFENCE_ADDED, callback);
    315             m.arg1 = monitoringType;
    316             mReaperHandler.sendMessage(m);
    317         } else {
    318             synchronized (mGeofences) {
    319                 mGeofences.remove(geofenceId);
    320             }
    321         }
    322 
    323         if (DEBUG) Log.d(TAG, "addCircularFence: Result is: " + result);
    324         return result;
    325     }
    326 
    327     public boolean removeGeofence(int geofenceId, int monitoringType) {
    328         // This API is not thread safe. Operations on the same geofence need to be serialized
    329         // by upper layers
    330         if (DEBUG) Log.d(TAG, "Remove Geofence: GeofenceId: " + geofenceId);
    331         boolean result = false;
    332 
    333         synchronized (mGeofences) {
    334             if (mGeofences.get(geofenceId) == null) {
    335                 throw new IllegalArgumentException("Geofence " + geofenceId + " not registered.");
    336             }
    337         }
    338         switch (monitoringType) {
    339             case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
    340                 if (mGpsService == null) return false;
    341                 try {
    342                     result = mGpsService.removeHardwareGeofence(geofenceId);
    343                 } catch (RemoteException e) {
    344                     Log.e(TAG, "RemoveGeofence: Remote Exception calling LocationManagerService");
    345                     result = false;
    346                 }
    347                 break;
    348             case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
    349                 if(mFusedService == null) {
    350                     return false;
    351                 }
    352                 try {
    353                     mFusedService.removeGeofences(new int[] { geofenceId });
    354                     result = true;
    355                 } catch(RemoteException e) {
    356                     Log.e(TAG, "RemoveGeofence: RemoteException calling LocationManagerService");
    357                     result = false;
    358                 }
    359                 break;
    360             default:
    361                 result = false;
    362         }
    363         if (DEBUG) Log.d(TAG, "removeGeofence: Result is: " + result);
    364         return result;
    365     }
    366 
    367     public boolean pauseGeofence(int geofenceId, int monitoringType) {
    368         // This API is not thread safe. Operations on the same geofence need to be serialized
    369         // by upper layers
    370         if (DEBUG) Log.d(TAG, "Pause Geofence: GeofenceId: " + geofenceId);
    371         boolean result;
    372         synchronized (mGeofences) {
    373             if (mGeofences.get(geofenceId) == null) {
    374                 throw new IllegalArgumentException("Geofence " + geofenceId + " not registered.");
    375             }
    376         }
    377         switch (monitoringType) {
    378             case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
    379                 if (mGpsService == null) return false;
    380                 try {
    381                     result = mGpsService.pauseHardwareGeofence(geofenceId);
    382                 } catch (RemoteException e) {
    383                     Log.e(TAG, "PauseGeofence: Remote Exception calling LocationManagerService");
    384                     result = false;
    385                 }
    386                 break;
    387             case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
    388                 if(mFusedService == null) {
    389                     return false;
    390                 }
    391                 try {
    392                     mFusedService.pauseMonitoringGeofence(geofenceId);
    393                     result = true;
    394                 } catch(RemoteException e) {
    395                     Log.e(TAG, "PauseGeofence: RemoteException calling LocationManagerService");
    396                     result = false;
    397                 }
    398                 break;
    399             default:
    400                 result = false;
    401         }
    402         if (DEBUG) Log.d(TAG, "pauseGeofence: Result is: " + result);
    403         return result;
    404     }
    405 
    406 
    407     public boolean resumeGeofence(int geofenceId,  int monitoringType, int monitorTransition) {
    408         // This API is not thread safe. Operations on the same geofence need to be serialized
    409         // by upper layers
    410         if (DEBUG) Log.d(TAG, "Resume Geofence: GeofenceId: " + geofenceId);
    411         boolean result;
    412         synchronized (mGeofences) {
    413             if (mGeofences.get(geofenceId) == null) {
    414                 throw new IllegalArgumentException("Geofence " + geofenceId + " not registered.");
    415             }
    416         }
    417         switch (monitoringType) {
    418             case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
    419                 if (mGpsService == null) return false;
    420                 try {
    421                     result = mGpsService.resumeHardwareGeofence(geofenceId, monitorTransition);
    422                 } catch (RemoteException e) {
    423                     Log.e(TAG, "ResumeGeofence: Remote Exception calling LocationManagerService");
    424                     result = false;
    425                 }
    426                 break;
    427             case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
    428                 if(mFusedService == null) {
    429                     return false;
    430                 }
    431                 try {
    432                     mFusedService.resumeMonitoringGeofence(geofenceId, monitorTransition);
    433                     result = true;
    434                 } catch(RemoteException e) {
    435                     Log.e(TAG, "ResumeGeofence: RemoteException calling LocationManagerService");
    436                     result = false;
    437                 }
    438                 break;
    439             default:
    440                 result = false;
    441         }
    442         if (DEBUG) Log.d(TAG, "resumeGeofence: Result is: " + result);
    443         return result;
    444     }
    445 
    446     public boolean registerForMonitorStateChangeCallback(int monitoringType,
    447             IGeofenceHardwareMonitorCallback callback) {
    448         Message reaperMessage =
    449                 mReaperHandler.obtainMessage(REAPER_MONITOR_CALLBACK_ADDED, callback);
    450         reaperMessage.arg1 = monitoringType;
    451         mReaperHandler.sendMessage(reaperMessage);
    452 
    453         Message m = mCallbacksHandler.obtainMessage(CALLBACK_ADD, callback);
    454         m.arg1 = monitoringType;
    455         mCallbacksHandler.sendMessage(m);
    456         return true;
    457     }
    458 
    459     public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
    460             IGeofenceHardwareMonitorCallback callback) {
    461         Message m = mCallbacksHandler.obtainMessage(CALLBACK_REMOVE, callback);
    462         m.arg1 = monitoringType;
    463         mCallbacksHandler.sendMessage(m);
    464         return true;
    465     }
    466 
    467     /**
    468      * Used to report geofence transitions
    469      */
    470     public void reportGeofenceTransition(
    471             int geofenceId,
    472             Location location,
    473             int transition,
    474             long transitionTimestamp,
    475             int monitoringType,
    476             int sourcesUsed) {
    477         if(location == null) {
    478             Log.e(TAG, String.format("Invalid Geofence Transition: location=null"));
    479             return;
    480         }
    481         if(DEBUG) {
    482             Log.d(
    483                     TAG,
    484                     "GeofenceTransition| " + location + ", transition:" + transition +
    485                     ", transitionTimestamp:" + transitionTimestamp + ", monitoringType:" +
    486                     monitoringType + ", sourcesUsed:" + sourcesUsed);
    487         }
    488 
    489         GeofenceTransition geofenceTransition = new GeofenceTransition(
    490                 geofenceId,
    491                 transition,
    492                 transitionTimestamp,
    493                 location,
    494                 monitoringType,
    495                 sourcesUsed);
    496         acquireWakeLock();
    497 
    498         Message message = mGeofenceHandler.obtainMessage(
    499                 GEOFENCE_TRANSITION_CALLBACK,
    500                 geofenceTransition);
    501         message.sendToTarget();
    502     }
    503 
    504     /**
    505      * Used to report Monitor status changes.
    506      */
    507     public void reportGeofenceMonitorStatus(
    508             int monitoringType,
    509             int monitoringStatus,
    510             Location location,
    511             int source) {
    512         setMonitorAvailability(monitoringType, monitoringStatus);
    513         acquireWakeLock();
    514         GeofenceHardwareMonitorEvent event = new GeofenceHardwareMonitorEvent(
    515                 monitoringType,
    516                 monitoringStatus,
    517                 source,
    518                 location);
    519         Message message = mCallbacksHandler.obtainMessage(GEOFENCE_STATUS, event);
    520         message.sendToTarget();
    521     }
    522 
    523     /**
    524      * Internal generic status report function for Geofence operations.
    525      *
    526      * @param operation The operation to be reported as defined internally.
    527      * @param geofenceId The id of the geofence the operation is related to.
    528      * @param operationStatus The status of the operation as defined in GeofenceHardware class. This
    529      *                        status is independent of the statuses reported by different HALs.
    530      */
    531     private void reportGeofenceOperationStatus(int operation, int geofenceId, int operationStatus) {
    532         acquireWakeLock();
    533         Message message = mGeofenceHandler.obtainMessage(operation);
    534         message.arg1 = geofenceId;
    535         message.arg2 = operationStatus;
    536         message.sendToTarget();
    537     }
    538 
    539     /**
    540      * Used to report the status of a Geofence Add operation.
    541      */
    542     public void reportGeofenceAddStatus(int geofenceId, int status) {
    543         if(DEBUG) Log.d(TAG, "AddCallback| id:" + geofenceId + ", status:" + status);
    544         reportGeofenceOperationStatus(ADD_GEOFENCE_CALLBACK, geofenceId, status);
    545     }
    546 
    547     /**
    548      * Used to report the status of a Geofence Remove operation.
    549      */
    550     public void reportGeofenceRemoveStatus(int geofenceId, int status) {
    551         if(DEBUG) Log.d(TAG, "RemoveCallback| id:" + geofenceId + ", status:" + status);
    552         reportGeofenceOperationStatus(REMOVE_GEOFENCE_CALLBACK, geofenceId, status);
    553     }
    554 
    555     /**
    556      * Used to report the status of a Geofence Pause operation.
    557      */
    558     public void reportGeofencePauseStatus(int geofenceId, int status) {
    559         if(DEBUG) Log.d(TAG, "PauseCallbac| id:" + geofenceId + ", status" + status);
    560         reportGeofenceOperationStatus(PAUSE_GEOFENCE_CALLBACK, geofenceId, status);
    561     }
    562 
    563     /**
    564      * Used to report the status of a Geofence Resume operation.
    565      */
    566     public void reportGeofenceResumeStatus(int geofenceId, int status) {
    567         if(DEBUG) Log.d(TAG, "ResumeCallback| id:" + geofenceId + ", status:" + status);
    568         reportGeofenceOperationStatus(RESUME_GEOFENCE_CALLBACK, geofenceId, status);
    569     }
    570 
    571     // All operations on mGeofences
    572     private Handler mGeofenceHandler = new Handler() {
    573         @Override
    574         public void handleMessage(Message msg) {
    575             int geofenceId;
    576             int status;
    577             IGeofenceHardwareCallback callback;
    578             switch (msg.what) {
    579                 case ADD_GEOFENCE_CALLBACK:
    580                     geofenceId = msg.arg1;
    581                     synchronized (mGeofences) {
    582                         callback = mGeofences.get(geofenceId);
    583                     }
    584 
    585                     if (callback != null) {
    586                         try {
    587                             callback.onGeofenceAdd(geofenceId, msg.arg2);
    588                         } catch (RemoteException e) {Log.i(TAG, "Remote Exception:" + e);}
    589                     }
    590                     releaseWakeLock();
    591                     break;
    592                 case REMOVE_GEOFENCE_CALLBACK:
    593                     geofenceId = msg.arg1;
    594                     synchronized (mGeofences) {
    595                         callback = mGeofences.get(geofenceId);
    596                     }
    597 
    598                     if (callback != null) {
    599                         try {
    600                             callback.onGeofenceRemove(geofenceId, msg.arg2);
    601                         } catch (RemoteException e) {}
    602                         IBinder callbackBinder = callback.asBinder();
    603                         boolean callbackInUse = false;
    604                         synchronized (mGeofences) {
    605                             mGeofences.remove(geofenceId);
    606                             // Check if the underlying binder is still useful for other geofences,
    607                             // if no, unlink the DeathRecipient to avoid memory leak.
    608                             for (int i = 0; i < mGeofences.size(); i++) {
    609                                  if (mGeofences.valueAt(i).asBinder() == callbackBinder) {
    610                                      callbackInUse = true;
    611                                      break;
    612                                  }
    613                             }
    614                         }
    615 
    616                         // Remove the reaper associated with this binder.
    617                         if (!callbackInUse) {
    618                             for (Iterator<Reaper> iterator = mReapers.iterator();
    619                                     iterator.hasNext();) {
    620                                 Reaper reaper = iterator.next();
    621                                 if (reaper.mCallback != null &&
    622                                         reaper.mCallback.asBinder() == callbackBinder) {
    623                                     iterator.remove();
    624                                     reaper.unlinkToDeath();
    625                                     if (DEBUG) Log.d(TAG, String.format("Removed reaper %s " +
    626                                           "because binder %s is no longer needed.",
    627                                           reaper, callbackBinder));
    628                                 }
    629                             }
    630                         }
    631                     }
    632                     releaseWakeLock();
    633                     break;
    634 
    635                 case PAUSE_GEOFENCE_CALLBACK:
    636                     geofenceId = msg.arg1;
    637                     synchronized (mGeofences) {
    638                         callback = mGeofences.get(geofenceId);
    639                     }
    640 
    641                     if (callback != null) {
    642                         try {
    643                             callback.onGeofencePause(geofenceId, msg.arg2);
    644                         } catch (RemoteException e) {}
    645                     }
    646                     releaseWakeLock();
    647                     break;
    648 
    649                 case RESUME_GEOFENCE_CALLBACK:
    650                     geofenceId = msg.arg1;
    651                     synchronized (mGeofences) {
    652                         callback = mGeofences.get(geofenceId);
    653                     }
    654 
    655                     if (callback != null) {
    656                         try {
    657                             callback.onGeofenceResume(geofenceId, msg.arg2);
    658                         } catch (RemoteException e) {}
    659                     }
    660                     releaseWakeLock();
    661                     break;
    662 
    663                 case GEOFENCE_TRANSITION_CALLBACK:
    664                     GeofenceTransition geofenceTransition = (GeofenceTransition)(msg.obj);
    665                     synchronized (mGeofences) {
    666                         callback = mGeofences.get(geofenceTransition.mGeofenceId);
    667 
    668                         // need to keep access to mGeofences synchronized at all times
    669                         if (DEBUG) Log.d(TAG, "GeofenceTransistionCallback: GPS : GeofenceId: " +
    670                                 geofenceTransition.mGeofenceId +
    671                                 " Transition: " + geofenceTransition.mTransition +
    672                                 " Location: " + geofenceTransition.mLocation + ":" + mGeofences);
    673                     }
    674 
    675                     if (callback != null) {
    676                         try {
    677                             callback.onGeofenceTransition(
    678                                     geofenceTransition.mGeofenceId, geofenceTransition.mTransition,
    679                                     geofenceTransition.mLocation, geofenceTransition.mTimestamp,
    680                                     geofenceTransition.mMonitoringType);
    681                         } catch (RemoteException e) {}
    682                     }
    683                     releaseWakeLock();
    684                     break;
    685                 case GEOFENCE_CALLBACK_BINDER_DIED:
    686                    // Find all geofences associated with this callback and remove them.
    687                    callback = (IGeofenceHardwareCallback) (msg.obj);
    688                    if (DEBUG) Log.d(TAG, "Geofence callback reaped:" + callback);
    689                    int monitoringType = msg.arg1;
    690                    synchronized (mGeofences) {
    691                        for (int i = 0; i < mGeofences.size(); i++) {
    692                             if (mGeofences.valueAt(i).equals(callback)) {
    693                                 geofenceId = mGeofences.keyAt(i);
    694                                 removeGeofence(mGeofences.keyAt(i), monitoringType);
    695                                 mGeofences.remove(geofenceId);
    696                             }
    697                        }
    698                    }
    699             }
    700         }
    701     };
    702 
    703     // All operations on mCallbacks
    704     private Handler mCallbacksHandler = new Handler() {
    705         @Override
    706         public void handleMessage(Message msg) {
    707             int monitoringType;
    708             ArrayList<IGeofenceHardwareMonitorCallback> callbackList;
    709             IGeofenceHardwareMonitorCallback callback;
    710 
    711             switch (msg.what) {
    712                 case GEOFENCE_STATUS:
    713                     GeofenceHardwareMonitorEvent event = (GeofenceHardwareMonitorEvent) msg.obj;
    714                     callbackList = mCallbacks[event.getMonitoringType()];
    715                     if (callbackList != null) {
    716                         if (DEBUG) Log.d(TAG, "MonitoringSystemChangeCallback: " + event);
    717 
    718                         for (IGeofenceHardwareMonitorCallback c : callbackList) {
    719                             try {
    720                                 c.onMonitoringSystemChange(event);
    721                             } catch (RemoteException e) {
    722                                 Log.d(TAG, "Error reporting onMonitoringSystemChange.", e);
    723                             }
    724                         }
    725                     }
    726                     releaseWakeLock();
    727                     break;
    728                 case CALLBACK_ADD:
    729                     monitoringType = msg.arg1;
    730                     callback = (IGeofenceHardwareMonitorCallback) msg.obj;
    731                     callbackList = mCallbacks[monitoringType];
    732                     if (callbackList == null) {
    733                         callbackList = new ArrayList<IGeofenceHardwareMonitorCallback>();
    734                         mCallbacks[monitoringType] = callbackList;
    735                     }
    736                     if (!callbackList.contains(callback)) callbackList.add(callback);
    737                     break;
    738                 case CALLBACK_REMOVE:
    739                     monitoringType = msg.arg1;
    740                     callback = (IGeofenceHardwareMonitorCallback) msg.obj;
    741                     callbackList = mCallbacks[monitoringType];
    742                     if (callbackList != null) {
    743                         callbackList.remove(callback);
    744                     }
    745                     break;
    746                 case MONITOR_CALLBACK_BINDER_DIED:
    747                     callback = (IGeofenceHardwareMonitorCallback) msg.obj;
    748                     if (DEBUG) Log.d(TAG, "Monitor callback reaped:" + callback);
    749                     callbackList = mCallbacks[msg.arg1];
    750                     if (callbackList != null && callbackList.contains(callback)) {
    751                         callbackList.remove(callback);
    752                     }
    753             }
    754         }
    755     };
    756 
    757     // All operations on mReaper
    758     private Handler mReaperHandler = new Handler() {
    759         @Override
    760         public void handleMessage(Message msg) {
    761             Reaper r;
    762             IGeofenceHardwareCallback callback;
    763             IGeofenceHardwareMonitorCallback monitorCallback;
    764             int monitoringType;
    765 
    766             switch (msg.what) {
    767                 case REAPER_GEOFENCE_ADDED:
    768                     callback = (IGeofenceHardwareCallback) msg.obj;
    769                     monitoringType = msg.arg1;
    770                     r = new Reaper(callback, monitoringType);
    771                     if (!mReapers.contains(r)) {
    772                         mReapers.add(r);
    773                         IBinder b = callback.asBinder();
    774                         try {
    775                             b.linkToDeath(r, 0);
    776                         } catch (RemoteException e) {}
    777                     }
    778                     break;
    779                 case REAPER_MONITOR_CALLBACK_ADDED:
    780                     monitorCallback = (IGeofenceHardwareMonitorCallback) msg.obj;
    781                     monitoringType = msg.arg1;
    782 
    783                     r = new Reaper(monitorCallback, monitoringType);
    784                     if (!mReapers.contains(r)) {
    785                         mReapers.add(r);
    786                         IBinder b = monitorCallback.asBinder();
    787                         try {
    788                             b.linkToDeath(r, 0);
    789                         } catch (RemoteException e) {}
    790                     }
    791                     break;
    792                 case REAPER_REMOVED:
    793                     r = (Reaper) msg.obj;
    794                     mReapers.remove(r);
    795             }
    796         }
    797     };
    798 
    799     private class GeofenceTransition {
    800         private int mGeofenceId, mTransition;
    801         private long mTimestamp;
    802         private Location mLocation;
    803         private int mMonitoringType;
    804         private int mSourcesUsed;
    805 
    806         GeofenceTransition(
    807                 int geofenceId,
    808                 int transition,
    809                 long timestamp,
    810                 Location location,
    811                 int monitoringType,
    812                 int sourcesUsed) {
    813             mGeofenceId = geofenceId;
    814             mTransition = transition;
    815             mTimestamp = timestamp;
    816             mLocation = location;
    817             mMonitoringType = monitoringType;
    818             mSourcesUsed = sourcesUsed;
    819         }
    820     }
    821 
    822     private void setMonitorAvailability(int monitor, int val) {
    823         synchronized (mSupportedMonitorTypes) {
    824             mSupportedMonitorTypes[monitor] = val;
    825         }
    826     }
    827 
    828 
    829     int getMonitoringResolutionLevel(int monitoringType) {
    830         switch (monitoringType) {
    831             case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
    832                 return RESOLUTION_LEVEL_FINE;
    833             case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
    834                 return RESOLUTION_LEVEL_FINE;
    835         }
    836         return RESOLUTION_LEVEL_NONE;
    837     }
    838 
    839     class Reaper implements IBinder.DeathRecipient {
    840         private IGeofenceHardwareMonitorCallback mMonitorCallback;
    841         private IGeofenceHardwareCallback mCallback;
    842         private int mMonitoringType;
    843 
    844         Reaper(IGeofenceHardwareCallback c, int monitoringType) {
    845             mCallback = c;
    846             mMonitoringType = monitoringType;
    847         }
    848 
    849         Reaper(IGeofenceHardwareMonitorCallback c, int monitoringType) {
    850             mMonitorCallback = c;
    851             mMonitoringType = monitoringType;
    852         }
    853 
    854         @Override
    855         public void binderDied() {
    856             Message m;
    857             if (mCallback != null) {
    858                 m = mGeofenceHandler.obtainMessage(GEOFENCE_CALLBACK_BINDER_DIED, mCallback);
    859                 m.arg1 = mMonitoringType;
    860                 mGeofenceHandler.sendMessage(m);
    861             } else if (mMonitorCallback != null) {
    862                 m = mCallbacksHandler.obtainMessage(MONITOR_CALLBACK_BINDER_DIED, mMonitorCallback);
    863                 m.arg1 = mMonitoringType;
    864                 mCallbacksHandler.sendMessage(m);
    865             }
    866             Message reaperMessage = mReaperHandler.obtainMessage(REAPER_REMOVED, this);
    867             mReaperHandler.sendMessage(reaperMessage);
    868         }
    869 
    870         @Override
    871         public int hashCode() {
    872             int result = 17;
    873             result = 31 * result + (mCallback != null ? mCallback.asBinder().hashCode() : 0);
    874             result = 31 * result + (mMonitorCallback != null
    875                     ? mMonitorCallback.asBinder().hashCode() : 0);
    876             result = 31 * result + mMonitoringType;
    877             return result;
    878         }
    879 
    880         @Override
    881         public boolean equals(Object obj) {
    882             if (obj == null) return false;
    883             if (obj == this) return true;
    884 
    885             Reaper rhs = (Reaper) obj;
    886             return binderEquals(rhs.mCallback, mCallback) &&
    887                     binderEquals(rhs.mMonitorCallback, mMonitorCallback) &&
    888                     rhs.mMonitoringType == mMonitoringType;
    889         }
    890 
    891         /**
    892          * Compares the underlying Binder of the given two IInterface objects and returns true if
    893          * they equals. null values are accepted.
    894          */
    895         private boolean binderEquals(IInterface left, IInterface right) {
    896           if (left == null) {
    897             return right == null;
    898           } else {
    899             return right == null ? false : left.asBinder() == right.asBinder();
    900           }
    901         }
    902 
    903         /**
    904          * Unlinks this DeathRecipient.
    905          */
    906         private boolean unlinkToDeath() {
    907           if (mMonitorCallback != null) {
    908             return mMonitorCallback.asBinder().unlinkToDeath(this, 0);
    909           } else if (mCallback != null) {
    910             return mCallback.asBinder().unlinkToDeath(this, 0);
    911           }
    912           return true;
    913         }
    914 
    915         private boolean callbackEquals(IGeofenceHardwareCallback cb) {
    916           return mCallback != null && mCallback.asBinder() == cb.asBinder();
    917         }
    918     }
    919 
    920     int getAllowedResolutionLevel(int pid, int uid) {
    921         if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
    922                 pid, uid) == PackageManager.PERMISSION_GRANTED) {
    923             return RESOLUTION_LEVEL_FINE;
    924         } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
    925                 pid, uid) == PackageManager.PERMISSION_GRANTED) {
    926             return RESOLUTION_LEVEL_COARSE;
    927         } else {
    928             return RESOLUTION_LEVEL_NONE;
    929         }
    930     }
    931 }
    932