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 */
16package android.hardware.location;
17
18import android.content.Context;
19import android.location.Location;
20import android.os.RemoteException;
21import android.util.Log;
22
23import java.lang.ref.WeakReference;
24import java.util.HashMap;
25import java.util.Map;
26import java.util.Set;
27
28
29/**
30 * This class handles geofences managed by various hardware subsystems. It contains
31 * the public APIs that is needed to accomplish the task.
32 *
33 * <p>The APIs should not be called directly by the app developers. A higher level api
34 * which abstracts the hardware should be used instead. All the checks are done by the higher
35 * level public API. Any needed locking should be handled by the higher level API.
36 *
37 * <p> There are 3 states associated with a Geofence: Inside, Outside, Unknown.
38 * There are 3 transitions: {@link #GEOFENCE_ENTERED}, {@link #GEOFENCE_EXITED},
39 * {@link #GEOFENCE_UNCERTAIN}. The APIs only expose the transitions.
40 *
41 * <p> Inside state: The hardware subsystem is reasonably confident that the user is inside
42 * the geofence. Outside state: The hardware subsystem is reasonably confident that the user
43 * is outside the geofence Unknown state: Unknown state can be interpreted as a state in which the
44 * monitoring subsystem isn't confident enough that the user is either inside or
45 * outside the Geofence. If the accuracy does not improve for a sufficient period of time,
46 * the {@link #GEOFENCE_UNCERTAIN} transition would be triggered. If the accuracy improves later,
47 * an appropriate transition would be triggered. The "reasonably confident" parameter
48 * depends on the hardware system and the positioning algorithms used.
49 * For instance, {@link #MONITORING_TYPE_GPS_HARDWARE} uses 95% as a confidence level.
50 */
51public final class GeofenceHardware {
52    private IGeofenceHardware mService;
53
54    // Hardware systems that do geofence monitoring.
55    static final int NUM_MONITORS = 1;
56
57    /**
58     * Constant for geofence monitoring done by the GPS hardware.
59     */
60    public static final int MONITORING_TYPE_GPS_HARDWARE = 0;
61
62    /**
63     * Constant to indiciate that the monitoring system is currently
64     * available for monitoring geofences.
65     */
66    public static final int MONITOR_CURRENTLY_AVAILABLE = 0;
67
68    /**
69     * Constant to indiciate that the monitoring system is currently
70     * unavailable for monitoring geofences.
71     */
72    public static final int MONITOR_CURRENTLY_UNAVAILABLE = 1;
73
74    /**
75     * Constant to indiciate that the monitoring system is unsupported
76     * for hardware geofence monitoring.
77     */
78    public static final int MONITOR_UNSUPPORTED = 2;
79
80    // The following constants need to match geofence flags in gps.h
81    /**
82     * The constant to indicate that the user has entered the geofence.
83     */
84    public static final int GEOFENCE_ENTERED = 1<<0L;
85
86    /**
87     * The constant to indicate that the user has exited the geofence.
88     */
89    public static final int GEOFENCE_EXITED = 1<<1L;
90
91    /**
92     * The constant to indicate that the user is uncertain with respect to a
93     * geofence.                                                  nn
94     */
95    public static final int GEOFENCE_UNCERTAIN = 1<<2L;
96
97    /**
98     * The constant used to indicate success of the particular geofence call
99     */
100    public static final int GEOFENCE_SUCCESS = 0;
101
102    /**
103     * The constant used to indicate that too many geofences have been registered.
104     */
105    public static final int GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 1;
106
107    /**
108     * The constant used to indicate that the geofence id already exists.
109     */
110    public static final int GEOFENCE_ERROR_ID_EXISTS  = 2;
111
112    /**
113     * The constant used to indicate that the geofence id is unknown.
114     */
115    public static final int GEOFENCE_ERROR_ID_UNKNOWN = 3;
116
117    /**
118     * The constant used to indicate that the transition requested for the geofence is invalid.
119     */
120    public static final int GEOFENCE_ERROR_INVALID_TRANSITION = 4;
121
122    /**
123     * The constant used to indicate that the geofence operation has failed.
124     */
125    public static final int GEOFENCE_FAILURE = 5;
126
127    static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
128    static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
129
130    private HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>
131            mCallbacks = new HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>();
132    private HashMap<GeofenceHardwareMonitorCallback, GeofenceHardwareMonitorCallbackWrapper>
133            mMonitorCallbacks = new HashMap<GeofenceHardwareMonitorCallback,
134                    GeofenceHardwareMonitorCallbackWrapper>();
135    /**
136     * @hide
137     */
138    public GeofenceHardware(IGeofenceHardware service) {
139        mService = service;
140    }
141
142    /**
143     * Returns all the hardware geofence monitoring systems which are supported
144     *
145     * <p> Call {@link #getStatusOfMonitoringType(int)} to know the current state
146     * of a monitoring system.
147     *
148     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
149     * geofencing in hardware.
150     *
151     * @return An array of all the monitoring types.
152     *         An array of length 0 is returned in case of errors.
153     */
154    public int[] getMonitoringTypes() {
155        try {
156            return mService.getMonitoringTypes();
157        } catch (RemoteException e) {
158        }
159        return new int[0];
160    }
161
162    /**
163     * Returns current status of a hardware geofence monitoring system.
164     *
165     * <p>Status can be one of {@link #MONITOR_CURRENTLY_AVAILABLE},
166     * {@link #MONITOR_CURRENTLY_UNAVAILABLE} or {@link #MONITOR_UNSUPPORTED}
167     *
168     * <p> Some supported hardware monitoring systems might not be available
169     * for monitoring geofences in certain scenarios. For example, when a user
170     * enters a building, the GPS hardware subsystem might not be able monitor
171     * geofences and will change from {@link #MONITOR_CURRENTLY_AVAILABLE} to
172     * {@link #MONITOR_CURRENTLY_UNAVAILABLE}.
173     *
174     * @param monitoringType
175     * @return Current status of the monitoring type.
176     */
177    public int getStatusOfMonitoringType(int monitoringType) {
178        try {
179            return mService.getStatusOfMonitoringType(monitoringType);
180        } catch (RemoteException e) {
181            return MONITOR_UNSUPPORTED;
182        }
183    }
184
185    /**
186     * Creates a circular geofence which is monitored by subsystems in the hardware.
187     *
188     * <p> When the device detects that is has entered, exited or is uncertain
189     * about the area specified by the geofence, the given callback will be called.
190     *
191     * <p> If this call returns true, it means that the geofence has been sent to the hardware.
192     * {@link GeofenceHardwareCallback#onGeofenceAdd} will be called with the result of the
193     * add call from the hardware. The {@link GeofenceHardwareCallback#onGeofenceAdd} will be
194     * called with the following parameters when a transition event occurs.
195     * <ul>
196     * <li> The geofence Id
197     * <li> The location object indicating the last known location.
198     * <li> The transition associated with the geofence. One of
199     *      {@link #GEOFENCE_ENTERED}, {@link #GEOFENCE_EXITED}, {@link #GEOFENCE_UNCERTAIN}
200     * <li> The timestamp when the geofence transition occured.
201     * <li> The monitoring type ({@link #MONITORING_TYPE_GPS_HARDWARE} is one such example)
202     *      that was used.
203     * </ul>
204     *
205     * <p> The geofence will be monitored by the subsystem specified by monitoring_type parameter.
206     * The application does not need to hold a wakelock when the monitoring
207     * is being done by the underlying hardware subsystem. If the same geofence Id is being
208     * monitored by two different monitoring systems, the same id can be used for both calls, as
209     * long as the same callback object is used.
210     *
211     * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
212     * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
213     *
214     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
215     * geofencing in hardware.
216     *
217     * <p>This API should not be called directly by the app developers. A higher level api
218     * which abstracts the hardware should be used instead. All the checks are done by the higher
219     * level public API. Any needed locking should be handled by the higher level API.
220     *
221     * <p> Create a geofence request object using the methods in {@link GeofenceHardwareRequest} to
222     * set all the characteristics of the geofence. Use the created GeofenceHardwareRequest object
223     * in this call.
224     *
225     * @param geofenceId The id associated with the geofence.
226     * @param monitoringType The type of the hardware subsystem that should be used
227     *        to monitor the geofence.
228     * @param geofenceRequest The {@link GeofenceHardwareRequest} object associated with the
229     *        geofence.
230     * @param callback {@link GeofenceHardwareCallback} that will be use to notify the
231     *        transition.
232     * @return true when the geofence is successfully sent to the hardware for addition.
233     * @throws IllegalArgumentException when the geofence request type is not supported.
234     */
235    public boolean addGeofence(int geofenceId, int monitoringType, GeofenceHardwareRequest
236            geofenceRequest, GeofenceHardwareCallback callback) {
237        try {
238            if (geofenceRequest.getType() == GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) {
239                return mService.addCircularFence(geofenceId, monitoringType,
240                        geofenceRequest.getLatitude(),
241                        geofenceRequest.getLongitude(), geofenceRequest.getRadius(),
242                        geofenceRequest.getLastTransition(),
243                        geofenceRequest.getMonitorTransitions(),
244                        geofenceRequest.getNotificationResponsiveness(),
245                        geofenceRequest.getUnknownTimer(),
246                        getCallbackWrapper(callback));
247            } else {
248                throw new IllegalArgumentException("Geofence Request type not supported");
249            }
250        } catch (RemoteException e) {
251        }
252        return false;
253    }
254
255    /**
256     * Removes a geofence added by {@link #addGeofence} call.
257     *
258     * <p> If this call returns true, it means that the geofence has been sent to the hardware.
259     * {@link GeofenceHardwareCallback#onGeofenceRemove} will be called with the result of the
260     * remove call from the hardware.
261     *
262     * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
263     * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
264     *
265     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
266     * geofencing in hardware.
267     *
268     * <p>This API should not be called directly by the app developers. A higher level api
269     * which abstracts the hardware should be used instead. All the checks are done by the higher
270     * level public API. Any needed locking should be handled by the higher level API.
271     *
272     * @param geofenceId The id of the geofence.
273     * @param monitoringType The type of the hardware subsystem that should be used
274     *        to monitor the geofence.
275     * @return true when the geofence is successfully sent to the hardware for removal.                     .
276     */
277   public boolean removeGeofence(int geofenceId, int monitoringType) {
278       try {
279           return mService.removeGeofence(geofenceId, monitoringType);
280       } catch (RemoteException e) {
281       }
282       return false;
283   }
284
285    /**
286     * Pauses the monitoring of a geofence added by {@link #addGeofence} call.
287     *
288     * <p> If this call returns true, it means that the geofence has been sent to the hardware.
289     * {@link GeofenceHardwareCallback#onGeofencePause} will be called with the result of the
290     * pause call from the hardware.
291     *
292     * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
293     * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
294     *
295     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
296     * geofencing in hardware.
297     *
298     * <p>This API should not be called directly by the app developers. A higher level api
299     * which abstracts the hardware should be used instead. All the checks are done by the higher
300     * level public API. Any needed locking should be handled by the higher level API.
301     *
302     * @param geofenceId The id of the geofence.
303     * @param monitoringType The type of the hardware subsystem that should be used
304     *        to monitor the geofence.
305     * @return true when the geofence is successfully sent to the hardware for pausing.
306     */
307    public boolean pauseGeofence(int geofenceId, int monitoringType) {
308        try {
309            return mService.pauseGeofence(geofenceId, monitoringType);
310        } catch (RemoteException e) {
311        }
312        return false;
313    }
314
315    /**
316     * Resumes the monitoring of a geofence added by {@link #pauseGeofence} call.
317     *
318     * <p> If this call returns true, it means that the geofence has been sent to the hardware.
319     * {@link GeofenceHardwareCallback#onGeofenceResume} will be called with the result of the
320     * resume call from the hardware.
321     *
322     * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
323     * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
324     *
325     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
326     * geofencing in hardware.
327     *
328     * <p>This API should not be called directly by the app developers. A higher level api
329     * which abstracts the hardware should be used instead. All the checks are done by the higher
330     * level public API. Any needed locking should be handled by the higher level API.
331     *
332     * @param geofenceId The id of the geofence.
333     * @param monitoringType The type of the hardware subsystem that should be used
334     *        to monitor the geofence.
335     * @param monitorTransition Bitwise OR of {@link #GEOFENCE_ENTERED},
336     *        {@link #GEOFENCE_EXITED}, {@link #GEOFENCE_UNCERTAIN}
337     * @return true when the geofence is successfully sent to the hardware for resumption.
338     */
339    public boolean resumeGeofence(int geofenceId, int monitoringType, int monitorTransition) {
340        try {
341            return mService.resumeGeofence(geofenceId, monitoringType, monitorTransition);
342        } catch (RemoteException e) {
343        }
344        return false;
345    }
346
347    /**
348     * Register the callback to be notified when the state of a hardware geofence
349     * monitoring system changes. For instance, it can change from
350     * {@link #MONITOR_CURRENTLY_AVAILABLE} to {@link #MONITOR_CURRENTLY_UNAVAILABLE}
351     *
352     * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
353     * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
354     *
355     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
356     * geofencing in hardware.
357     *
358     * <p>This API should not be called directly by the app developers. A higher level api
359     * which abstracts the hardware should be used instead. All the checks are done by the higher
360     * level public API. Any needed locking should be handled by the higher level API.
361     *
362     * <p> The same callback object can be used to be informed of geofence transitions
363     * and state changes of the underlying hardware subsystem.
364     *
365     * @param monitoringType Type of the monitor
366     * @param callback Callback that will be called.
367     * @return true on success
368     */
369    public boolean registerForMonitorStateChangeCallback(int monitoringType,
370            GeofenceHardwareMonitorCallback callback) {
371        try {
372            return mService.registerForMonitorStateChangeCallback(monitoringType,
373                    getMonitorCallbackWrapper(callback));
374        } catch (RemoteException e) {
375        }
376        return false;
377    }
378
379    /**
380     * Unregister the callback that was used with {@link #registerForMonitorStateChangeCallback}
381     * to notify when the state of the hardware geofence monitoring system changes.
382     *
383     * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
384     * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
385     *
386     * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
387     * geofencing in hardware.
388     *
389     * <p>This API should not be called directly by the app developers. A higher level api
390     * which abstracts the hardware should be used instead. All the checks are done by the higher
391     * level public API. Any needed locking should be handled by the higher level API.
392     *
393     * @param monitoringType Type of the monitor
394     * @param callback Callback that will be called.
395     * @return true on success
396     */
397    public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
398            GeofenceHardwareMonitorCallback callback) {
399        boolean  result = false;
400        try {
401            result = mService.unregisterForMonitorStateChangeCallback(monitoringType,
402                    getMonitorCallbackWrapper(callback));
403            if (result) removeMonitorCallback(callback);
404
405        } catch (RemoteException e) {
406        }
407        return result;
408    }
409
410
411    private void removeCallback(GeofenceHardwareCallback callback) {
412        synchronized (mCallbacks) {
413            mCallbacks.remove(callback);
414        }
415    }
416
417    private GeofenceHardwareCallbackWrapper getCallbackWrapper(GeofenceHardwareCallback callback) {
418        synchronized (mCallbacks) {
419            GeofenceHardwareCallbackWrapper wrapper = mCallbacks.get(callback);
420            if (wrapper == null) {
421                wrapper = new GeofenceHardwareCallbackWrapper(callback);
422                mCallbacks.put(callback, wrapper);
423            }
424            return wrapper;
425        }
426    }
427
428    private void removeMonitorCallback(GeofenceHardwareMonitorCallback callback) {
429        synchronized (mMonitorCallbacks) {
430            mMonitorCallbacks.remove(callback);
431        }
432    }
433
434    private GeofenceHardwareMonitorCallbackWrapper getMonitorCallbackWrapper(
435            GeofenceHardwareMonitorCallback callback) {
436        synchronized (mMonitorCallbacks) {
437            GeofenceHardwareMonitorCallbackWrapper wrapper = mMonitorCallbacks.get(callback);
438            if (wrapper == null) {
439                wrapper = new GeofenceHardwareMonitorCallbackWrapper(callback);
440                mMonitorCallbacks.put(callback, wrapper);
441            }
442            return wrapper;
443        }
444    }
445
446    class GeofenceHardwareMonitorCallbackWrapper extends IGeofenceHardwareMonitorCallback.Stub {
447        private WeakReference<GeofenceHardwareMonitorCallback> mCallback;
448
449        GeofenceHardwareMonitorCallbackWrapper(GeofenceHardwareMonitorCallback c) {
450            mCallback = new WeakReference<GeofenceHardwareMonitorCallback>(c);
451        }
452
453        public void onMonitoringSystemChange(int monitoringType, boolean available,
454                Location location) {
455            GeofenceHardwareMonitorCallback c = mCallback.get();
456            if (c != null) c.onMonitoringSystemChange(monitoringType, available, location);
457        }
458    }
459
460    class GeofenceHardwareCallbackWrapper extends IGeofenceHardwareCallback.Stub {
461        private WeakReference<GeofenceHardwareCallback> mCallback;
462
463        GeofenceHardwareCallbackWrapper(GeofenceHardwareCallback c) {
464            mCallback = new WeakReference<GeofenceHardwareCallback>(c);
465        }
466
467        public void onGeofenceTransition(int geofenceId, int transition, Location location,
468                long timestamp, int monitoringType) {
469            GeofenceHardwareCallback c = mCallback.get();
470            if (c != null) {
471                c.onGeofenceTransition(geofenceId, transition, location, timestamp,
472                        monitoringType);
473            }
474        }
475
476        public void onGeofenceAdd(int geofenceId, int status) {
477            GeofenceHardwareCallback c = mCallback.get();
478            if (c != null) c.onGeofenceAdd(geofenceId, status);
479        }
480
481        public void onGeofenceRemove(int geofenceId, int status) {
482            GeofenceHardwareCallback c = mCallback.get();
483            if (c != null) {
484                c.onGeofenceRemove(geofenceId, status);
485                removeCallback(c);
486            }
487        }
488
489        public void onGeofencePause(int geofenceId, int status) {
490            GeofenceHardwareCallback c = mCallback.get();
491            if (c != null) {
492                c.onGeofencePause(geofenceId, status);
493            }
494        }
495
496        public void onGeofenceResume(int geofenceId, int status) {
497            GeofenceHardwareCallback c = mCallback.get();
498            if (c != null) c.onGeofenceResume(geofenceId, status);
499        }
500    }
501}
502