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
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