content: /*
   * 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.annotation.UnsupportedAppUsage;
  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>();
  
      /** @hide */
      @UnsupportedAppUsage
      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);
          }
      }
  }
  