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