/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.hardware.location; import android.annotation.SystemApi; import android.location.Location; import android.os.Build; import android.os.RemoteException; import java.lang.ref.WeakReference; import java.util.HashMap; /** * This class handles geofences managed by various hardware subsystems. It contains * the public APIs that is needed to accomplish the task. * * <p>The APIs should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * <p> There are 3 states associated with a Geofence: Inside, Outside, Unknown. * There are 3 transitions: {@link #GEOFENCE_ENTERED}, {@link #GEOFENCE_EXITED}, * {@link #GEOFENCE_UNCERTAIN}. The APIs only expose the transitions. * * <p> Inside state: The hardware subsystem is reasonably confident that the user is inside * the geofence. Outside state: The hardware subsystem is reasonably confident that the user * is outside the geofence Unknown state: Unknown state can be interpreted as a state in which the * monitoring subsystem isn't confident enough that the user is either inside or * outside the Geofence. If the accuracy does not improve for a sufficient period of time, * the {@link #GEOFENCE_UNCERTAIN} transition would be triggered. If the accuracy improves later, * an appropriate transition would be triggered. The "reasonably confident" parameter * depends on the hardware system and the positioning algorithms used. * For instance, {@link #MONITORING_TYPE_GPS_HARDWARE} uses 95% as a confidence level. * * @hide */ @SystemApi public final class GeofenceHardware { private IGeofenceHardware mService; // Hardware systems that do geofence monitoring. static final int NUM_MONITORS = 2; /** * Constant for geofence monitoring done by the GPS hardware. */ public static final int MONITORING_TYPE_GPS_HARDWARE = 0; /** * Constant for geofence monitoring done by the Fused hardware. */ public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; /** * Constant to indicate that the monitoring system is currently * available for monitoring geofences. */ public static final int MONITOR_CURRENTLY_AVAILABLE = 0; /** * Constant to indicate that the monitoring system is currently * unavailable for monitoring geofences. */ public static final int MONITOR_CURRENTLY_UNAVAILABLE = 1; /** * Constant to indicate that the monitoring system is unsupported * for hardware geofence monitoring. */ public static final int MONITOR_UNSUPPORTED = 2; // The following constants need to match geofence flags in gps.h and fused_location.h /** * The constant to indicate that the user has entered the geofence. */ public static final int GEOFENCE_ENTERED = 1<<0L; /** * The constant to indicate that the user has exited the geofence. */ public static final int GEOFENCE_EXITED = 1<<1L; /** * The constant to indicate that the user is uncertain with respect to a * geofence. */ public static final int GEOFENCE_UNCERTAIN = 1<<2L; /** * The constant used to indicate success of the particular geofence call */ public static final int GEOFENCE_SUCCESS = 0; /** * The constant used to indicate that too many geofences have been registered. */ public static final int GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 1; /** * The constant used to indicate that the geofence id already exists. */ public static final int GEOFENCE_ERROR_ID_EXISTS = 2; /** * The constant used to indicate that the geofence id is unknown. */ public static final int GEOFENCE_ERROR_ID_UNKNOWN = 3; /** * The constant used to indicate that the transition requested for the geofence is invalid. */ public static final int GEOFENCE_ERROR_INVALID_TRANSITION = 4; /** * The constant used to indicate that the geofence operation has failed. */ public static final int GEOFENCE_FAILURE = 5; /** * The constant used to indicate that the operation failed due to insufficient memory. */ public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; // the following values must match the definitions in fused_location.h /** * The constant used to indicate that the monitoring system supports GNSS. */ public static final int SOURCE_TECHNOLOGY_GNSS = (1<<0); /** * The constant used to indicate that the monitoring system supports WiFi. */ public static final int SOURCE_TECHNOLOGY_WIFI = (1<<1); /** * The constant used to indicate that the monitoring system supports Sensors. */ public static final int SOURCE_TECHNOLOGY_SENSORS = (1<<2); /** * The constant used to indicate that the monitoring system supports Cell. */ public static final int SOURCE_TECHNOLOGY_CELL = (1<<3); /** * The constant used to indicate that the monitoring system supports Bluetooth. */ public static final int SOURCE_TECHNOLOGY_BLUETOOTH = (1<<4); private HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper> mCallbacks = new HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>(); private HashMap<GeofenceHardwareMonitorCallback, GeofenceHardwareMonitorCallbackWrapper> mMonitorCallbacks = new HashMap<GeofenceHardwareMonitorCallback, GeofenceHardwareMonitorCallbackWrapper>(); public GeofenceHardware(IGeofenceHardware service) { mService = service; } /** * Returns all the hardware geofence monitoring systems which are supported * * <p> Call {@link #getStatusOfMonitoringType(int)} to know the current state * of a monitoring system. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * @return An array of all the monitoring types. * An array of length 0 is returned in case of errors. */ public int[] getMonitoringTypes() { try { return mService.getMonitoringTypes(); } catch (RemoteException e) { } return new int[0]; } /** * Returns current status of a hardware geofence monitoring system. * * <p>Status can be one of {@link #MONITOR_CURRENTLY_AVAILABLE}, * {@link #MONITOR_CURRENTLY_UNAVAILABLE} or {@link #MONITOR_UNSUPPORTED} * * <p> Some supported hardware monitoring systems might not be available * for monitoring geofences in certain scenarios. For example, when a user * enters a building, the GPS hardware subsystem might not be able monitor * geofences and will change from {@link #MONITOR_CURRENTLY_AVAILABLE} to * {@link #MONITOR_CURRENTLY_UNAVAILABLE}. * * @param monitoringType * @return Current status of the monitoring type. */ public int getStatusOfMonitoringType(int monitoringType) { try { return mService.getStatusOfMonitoringType(monitoringType); } catch (RemoteException e) { return MONITOR_UNSUPPORTED; } } /** * Creates a circular geofence which is monitored by subsystems in the hardware. * * <p> When the device detects that is has entered, exited or is uncertain * about the area specified by the geofence, the given callback will be called. * * <p> If this call returns true, it means that the geofence has been sent to the hardware. * {@link GeofenceHardwareCallback#onGeofenceAdd} will be called with the result of the * add call from the hardware. The {@link GeofenceHardwareCallback#onGeofenceAdd} will be * called with the following parameters when a transition event occurs. * <ul> * <li> The geofence Id * <li> The location object indicating the last known location. * <li> The transition associated with the geofence. One of * {@link #GEOFENCE_ENTERED}, {@link #GEOFENCE_EXITED}, {@link #GEOFENCE_UNCERTAIN} * <li> The timestamp when the geofence transition occured. * <li> The monitoring type ({@link #MONITORING_TYPE_GPS_HARDWARE} is one such example) * that was used. * </ul> * * <p> The geofence will be monitored by the subsystem specified by monitoring_type parameter. * The application does not need to hold a wakelock when the monitoring * is being done by the underlying hardware subsystem. If the same geofence Id is being * monitored by two different monitoring systems, the same id can be used for both calls, as * long as the same callback object is used. * * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when * {@link #MONITORING_TYPE_GPS_HARDWARE} is used. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * <p>This API should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * <p> Create a geofence request object using the methods in {@link GeofenceHardwareRequest} to * set all the characteristics of the geofence. Use the created GeofenceHardwareRequest object * in this call. * * @param geofenceId The id associated with the geofence. * @param monitoringType The type of the hardware subsystem that should be used * to monitor the geofence. * @param geofenceRequest The {@link GeofenceHardwareRequest} object associated with the * geofence. * @param callback {@link GeofenceHardwareCallback} that will be use to notify the * transition. * @return true when the geofence is successfully sent to the hardware for addition. * @throws IllegalArgumentException when the geofence request type is not supported. */ public boolean addGeofence(int geofenceId, int monitoringType, GeofenceHardwareRequest geofenceRequest, GeofenceHardwareCallback callback) { try { if (geofenceRequest.getType() == GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) { return mService.addCircularFence( monitoringType, new GeofenceHardwareRequestParcelable(geofenceId, geofenceRequest), getCallbackWrapper(callback)); } else { throw new IllegalArgumentException("Geofence Request type not supported"); } } catch (RemoteException e) { } return false; } /** * Removes a geofence added by {@link #addGeofence} call. * * <p> If this call returns true, it means that the geofence has been sent to the hardware. * {@link GeofenceHardwareCallback#onGeofenceRemove} will be called with the result of the * remove call from the hardware. * * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when * {@link #MONITORING_TYPE_GPS_HARDWARE} is used. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * <p>This API should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * @param geofenceId The id of the geofence. * @param monitoringType The type of the hardware subsystem that should be used * to monitor the geofence. * @return true when the geofence is successfully sent to the hardware for removal. . */ public boolean removeGeofence(int geofenceId, int monitoringType) { try { return mService.removeGeofence(geofenceId, monitoringType); } catch (RemoteException e) { } return false; } /** * Pauses the monitoring of a geofence added by {@link #addGeofence} call. * * <p> If this call returns true, it means that the geofence has been sent to the hardware. * {@link GeofenceHardwareCallback#onGeofencePause} will be called with the result of the * pause call from the hardware. * * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when * {@link #MONITORING_TYPE_GPS_HARDWARE} is used. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * <p>This API should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * @param geofenceId The id of the geofence. * @param monitoringType The type of the hardware subsystem that should be used * to monitor the geofence. * @return true when the geofence is successfully sent to the hardware for pausing. */ public boolean pauseGeofence(int geofenceId, int monitoringType) { try { return mService.pauseGeofence(geofenceId, monitoringType); } catch (RemoteException e) { } return false; } /** * Resumes the monitoring of a geofence added by {@link #pauseGeofence} call. * * <p> If this call returns true, it means that the geofence has been sent to the hardware. * {@link GeofenceHardwareCallback#onGeofenceResume} will be called with the result of the * resume call from the hardware. * * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when * {@link #MONITORING_TYPE_GPS_HARDWARE} is used. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * <p>This API should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * @param geofenceId The id of the geofence. * @param monitoringType The type of the hardware subsystem that should be used * to monitor the geofence. * @param monitorTransition Bitwise OR of {@link #GEOFENCE_ENTERED}, * {@link #GEOFENCE_EXITED}, {@link #GEOFENCE_UNCERTAIN} * @return true when the geofence is successfully sent to the hardware for resumption. */ public boolean resumeGeofence(int geofenceId, int monitoringType, int monitorTransition) { try { return mService.resumeGeofence(geofenceId, monitoringType, monitorTransition); } catch (RemoteException e) { } return false; } /** * Register the callback to be notified when the state of a hardware geofence * monitoring system changes. For instance, it can change from * {@link #MONITOR_CURRENTLY_AVAILABLE} to {@link #MONITOR_CURRENTLY_UNAVAILABLE} * * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when * {@link #MONITORING_TYPE_GPS_HARDWARE} is used. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * <p>This API should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * <p> The same callback object can be used to be informed of geofence transitions * and state changes of the underlying hardware subsystem. * * @param monitoringType Type of the monitor * @param callback Callback that will be called. * @return true on success */ public boolean registerForMonitorStateChangeCallback(int monitoringType, GeofenceHardwareMonitorCallback callback) { try { return mService.registerForMonitorStateChangeCallback(monitoringType, getMonitorCallbackWrapper(callback)); } catch (RemoteException e) { } return false; } /** * Unregister the callback that was used with {@link #registerForMonitorStateChangeCallback} * to notify when the state of the hardware geofence monitoring system changes. * * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when * {@link #MONITORING_TYPE_GPS_HARDWARE} is used. * * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access * geofencing in hardware. * * <p>This API should not be called directly by the app developers. A higher level api * which abstracts the hardware should be used instead. All the checks are done by the higher * level public API. Any needed locking should be handled by the higher level API. * * @param monitoringType Type of the monitor * @param callback Callback that will be called. * @return true on success */ public boolean unregisterForMonitorStateChangeCallback(int monitoringType, GeofenceHardwareMonitorCallback callback) { boolean result = false; try { result = mService.unregisterForMonitorStateChangeCallback(monitoringType, getMonitorCallbackWrapper(callback)); if (result) removeMonitorCallback(callback); } catch (RemoteException e) { } return result; } private void removeCallback(GeofenceHardwareCallback callback) { synchronized (mCallbacks) { mCallbacks.remove(callback); } } private GeofenceHardwareCallbackWrapper getCallbackWrapper(GeofenceHardwareCallback callback) { synchronized (mCallbacks) { GeofenceHardwareCallbackWrapper wrapper = mCallbacks.get(callback); if (wrapper == null) { wrapper = new GeofenceHardwareCallbackWrapper(callback); mCallbacks.put(callback, wrapper); } return wrapper; } } private void removeMonitorCallback(GeofenceHardwareMonitorCallback callback) { synchronized (mMonitorCallbacks) { mMonitorCallbacks.remove(callback); } } private GeofenceHardwareMonitorCallbackWrapper getMonitorCallbackWrapper( GeofenceHardwareMonitorCallback callback) { synchronized (mMonitorCallbacks) { GeofenceHardwareMonitorCallbackWrapper wrapper = mMonitorCallbacks.get(callback); if (wrapper == null) { wrapper = new GeofenceHardwareMonitorCallbackWrapper(callback); mMonitorCallbacks.put(callback, wrapper); } return wrapper; } } class GeofenceHardwareMonitorCallbackWrapper extends IGeofenceHardwareMonitorCallback.Stub { private WeakReference<GeofenceHardwareMonitorCallback> mCallback; GeofenceHardwareMonitorCallbackWrapper(GeofenceHardwareMonitorCallback c) { mCallback = new WeakReference<GeofenceHardwareMonitorCallback>(c); } public void onMonitoringSystemChange(GeofenceHardwareMonitorEvent event) { GeofenceHardwareMonitorCallback c = mCallback.get(); if (c == null) return; // report the legacy event first, so older clients are not broken c.onMonitoringSystemChange( event.getMonitoringType(), event.getMonitoringStatus() == GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE, event.getLocation()); // and only call the updated callback on on L and above, this complies with the // documentation of GeofenceHardwareMonitorCallback if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { c.onMonitoringSystemChange(event); } } } class GeofenceHardwareCallbackWrapper extends IGeofenceHardwareCallback.Stub { private WeakReference<GeofenceHardwareCallback> mCallback; GeofenceHardwareCallbackWrapper(GeofenceHardwareCallback c) { mCallback = new WeakReference<GeofenceHardwareCallback>(c); } public void onGeofenceTransition(int geofenceId, int transition, Location location, long timestamp, int monitoringType) { GeofenceHardwareCallback c = mCallback.get(); if (c != null) { c.onGeofenceTransition(geofenceId, transition, location, timestamp, monitoringType); } } public void onGeofenceAdd(int geofenceId, int status) { GeofenceHardwareCallback c = mCallback.get(); if (c != null) c.onGeofenceAdd(geofenceId, status); } public void onGeofenceRemove(int geofenceId, int status) { GeofenceHardwareCallback c = mCallback.get(); if (c != null) { c.onGeofenceRemove(geofenceId, status); removeCallback(c); } } public void onGeofencePause(int geofenceId, int status) { GeofenceHardwareCallback c = mCallback.get(); if (c != null) { c.onGeofencePause(geofenceId, status); } } public void onGeofenceResume(int geofenceId, int status) { GeofenceHardwareCallback c = mCallback.get(); if (c != null) c.onGeofenceResume(geofenceId, status); } } }