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