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
17package android.hardware.location;
18
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.location.IFusedGeofenceHardware;
22import android.location.IGpsGeofenceHardware;
23import android.location.Location;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.IInterface;
27import android.os.Message;
28import android.os.PowerManager;
29import android.os.RemoteException;
30import android.util.Log;
31import android.util.SparseArray;
32
33import java.util.ArrayList;
34import java.util.Iterator;
35
36/**
37 * This class manages the geofences which are handled by hardware.
38 *
39 * @hide
40 */
41public 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