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