content: /*
   * Copyright (C) 2012 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;
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.MemoryFile;
  import android.os.MessageQueue;
  import android.util.Log;
  import android.util.SparseArray;
  import android.util.SparseBooleanArray;
  import android.util.SparseIntArray;
  
  import com.android.internal.annotations.GuardedBy;
  
  import dalvik.system.CloseGuard;
  
  import java.io.IOException;
  import java.io.UncheckedIOException;
  import java.lang.ref.WeakReference;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Sensor manager implementation that communicates with the built-in
   * system sensors.
   *
   * @hide
   */
  public class SystemSensorManager extends SensorManager {
      //TODO: disable extra logging before release
      private static final boolean DEBUG_DYNAMIC_SENSOR = true;
      private static final int MIN_DIRECT_CHANNEL_BUFFER_SIZE = 104;
      private static final int MAX_LISTENER_COUNT = 128;
  
      private static native void nativeClassInit();
      private static native long nativeCreate(String opPackageName);
      private static native boolean nativeGetSensorAtIndex(long nativeInstance,
              Sensor sensor, int index);
      private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
      private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
  
      private static native int nativeCreateDirectChannel(
              long nativeInstance, long size, int channelType, int fd, HardwareBuffer buffer);
      private static native void nativeDestroyDirectChannel(
              long nativeInstance, int channelHandle);
      private static native int nativeConfigDirectChannel(
              long nativeInstance, int channelHandle, int sensorHandle, int rate);
  
      private static native int nativeSetOperationParameter(
              long nativeInstance, int handle, int type, float[] floatValues, int[] intValues);
  
      private static final Object sLock = new Object();
      @GuardedBy("sLock")
      private static boolean sNativeClassInited = false;
      @GuardedBy("sLock")
      private static InjectEventQueue sInjectEventQueue = null;
  
      private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
      private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
      private boolean mDynamicSensorListDirty = true;
  
      private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
  
      // Listener list
      private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners =
              new HashMap<SensorEventListener, SensorEventQueue>();
      private final HashMap<TriggerEventListener, TriggerEventQueue> mTriggerListeners =
              new HashMap<TriggerEventListener, TriggerEventQueue>();
  
      // Dynamic Sensor callbacks
      private HashMap<DynamicSensorCallback, Handler>
              mDynamicSensorCallbacks = new HashMap<>();
      private BroadcastReceiver mDynamicSensorBroadcastReceiver;
  
      // Looper associated with the context in which this instance was created.
      private final Looper mMainLooper;
      private final int mTargetSdkLevel;
      private final Context mContext;
      private final long mNativeInstance;
  
      /** {@hide} */
      public SystemSensorManager(Context context, Looper mainLooper) {
          synchronized (sLock) {
              if (!sNativeClassInited) {
                  sNativeClassInited = true;
                  nativeClassInit();
              }
          }
  
          mMainLooper = mainLooper;
          mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion;
          mContext = context;
          mNativeInstance = nativeCreate(context.getOpPackageName());
  
          // initialize the sensor list
          for (int index = 0;; ++index) {
              Sensor sensor = new Sensor();
              if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
              mFullSensorsList.add(sensor);
              mHandleToSensor.put(sensor.getHandle(), sensor);
          }
      }
  
  
      /** @hide */
      @Override
      protected List<Sensor> getFullSensorList() {
          return mFullSensorsList;
      }
  
      /** @hide */
      @Override
      protected List<Sensor> getFullDynamicSensorList() {
          // only set up broadcast receiver if the application tries to find dynamic sensors or
          // explicitly register a DynamicSensorCallback
          setupDynamicSensorBroadcastReceiver();
          updateDynamicSensorList();
          return mFullDynamicSensorsList;
      }
  
      /** @hide */
      @Override
      protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
              int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {
          if (listener == null || sensor == null) {
              Log.e(TAG, "sensor or listener is null");
              return false;
          }
          // Trigger Sensors should use the requestTriggerSensor call.
          if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
              Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
              return false;
          }
          if (maxBatchReportLatencyUs < 0 || delayUs < 0) {
              Log.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative");
              return false;
          }
          if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
              throw new IllegalStateException("register failed, "
                  + "the sensor listeners size has exceeded the maximum limit "
                  + MAX_LISTENER_COUNT);
          }
  
          // Invariants to preserve:
          // - one Looper per SensorEventListener
          // - one Looper per SensorEventQueue
          // We map SensorEventListener to a SensorEventQueue, which holds the looper
          synchronized (mSensorListeners) {
              SensorEventQueue queue = mSensorListeners.get(listener);
              if (queue == null) {
                  Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
                  final String fullClassName =
                          listener.getClass().getEnclosingClass() != null
                              ? listener.getClass().getEnclosingClass().getName()
                              : listener.getClass().getName();
                  queue = new SensorEventQueue(listener, looper, this, fullClassName);
                  if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
                      queue.dispose();
                      return false;
                  }
                  mSensorListeners.put(listener, queue);
                  return true;
              } else {
                  return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);
              }
          }
      }
  
      /** @hide */
      @Override
      protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
          // Trigger Sensors should use the cancelTriggerSensor call.
          if (sensor != null && sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
              return;
          }
  
          synchronized (mSensorListeners) {
              SensorEventQueue queue = mSensorListeners.get(listener);
              if (queue != null) {
                  boolean result;
                  if (sensor == null) {
                      result = queue.removeAllSensors();
                  } else {
                      result = queue.removeSensor(sensor, true);
                  }
                  if (result && !queue.hasSensors()) {
                      mSensorListeners.remove(listener);
                      queue.dispose();
                  }
              }
          }
      }
  
      /** @hide */
      @Override
      protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
          if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
  
          if (listener == null) throw new IllegalArgumentException("listener cannot be null");
  
          if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false;
  
          if (mTriggerListeners.size() >= MAX_LISTENER_COUNT) {
              throw new IllegalStateException("request failed, "
                      + "the trigger listeners size has exceeded the maximum limit "
                      + MAX_LISTENER_COUNT);
          }
  
          synchronized (mTriggerListeners) {
              TriggerEventQueue queue = mTriggerListeners.get(listener);
              if (queue == null) {
                  final String fullClassName =
                          listener.getClass().getEnclosingClass() != null
                              ? listener.getClass().getEnclosingClass().getName()
                              : listener.getClass().getName();
                  queue = new TriggerEventQueue(listener, mMainLooper, this, fullClassName);
                  if (!queue.addSensor(sensor, 0, 0)) {
                      queue.dispose();
                      return false;
                  }
                  mTriggerListeners.put(listener, queue);
                  return true;
              } else {
                  return queue.addSensor(sensor, 0, 0);
              }
          }
      }
  
      /** @hide */
      @Override
      protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
              boolean disable) {
          if (sensor != null && sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) {
              return false;
          }
          synchronized (mTriggerListeners) {
              TriggerEventQueue queue = mTriggerListeners.get(listener);
              if (queue != null) {
                  boolean result;
                  if (sensor == null) {
                      result = queue.removeAllSensors();
                  } else {
                      result = queue.removeSensor(sensor, disable);
                  }
                  if (result && !queue.hasSensors()) {
                      mTriggerListeners.remove(listener);
                      queue.dispose();
                  }
                  return result;
              }
              return false;
          }
      }
  
      protected boolean flushImpl(SensorEventListener listener) {
          if (listener == null) throw new IllegalArgumentException("listener cannot be null");
  
          synchronized (mSensorListeners) {
              SensorEventQueue queue = mSensorListeners.get(listener);
              if (queue == null) {
                  return false;
              } else {
                  return (queue.flush() == 0);
              }
          }
      }
  
      protected boolean initDataInjectionImpl(boolean enable) {
          synchronized (sLock) {
              if (enable) {
                  boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
                  // The HAL does not support injection OR SensorService hasn't been set in DI mode.
                  if (!isDataInjectionModeEnabled) {
                      Log.e(TAG, "Data Injection mode not enabled");
                      return false;
                  }
                  // Initialize a client for data_injection.
                  if (sInjectEventQueue == null) {
                      try {
                          sInjectEventQueue = new InjectEventQueue(
                                  mMainLooper, this, mContext.getPackageName());
                      } catch (RuntimeException e) {
                          Log.e(TAG, "Cannot create InjectEventQueue: " + e);
                      }
                  }
                  return sInjectEventQueue != null;
              } else {
                  // If data injection is being disabled clean up the native resources.
                  if (sInjectEventQueue != null) {
                      sInjectEventQueue.dispose();
                      sInjectEventQueue = null;
                  }
                  return true;
              }
          }
      }
  
      protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
              long timestamp) {
          synchronized (sLock) {
              if (sInjectEventQueue == null) {
                  Log.e(TAG, "Data injection mode not activated before calling injectSensorData");
                  return false;
              }
              int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,
                                                           timestamp);
              // If there are any errors in data injection clean up the native resources.
              if (ret != 0) {
                  sInjectEventQueue.dispose();
                  sInjectEventQueue = null;
              }
              return ret == 0;
          }
      }
  
      private void cleanupSensorConnection(Sensor sensor) {
          mHandleToSensor.remove(sensor.getHandle());
  
          if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
              synchronized (mTriggerListeners) {
                  HashMap<TriggerEventListener, TriggerEventQueue> triggerListeners =
                          new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
  
                  for (TriggerEventListener l : triggerListeners.keySet()) {
                      if (DEBUG_DYNAMIC_SENSOR) {
                          Log.i(TAG, "removed trigger listener" + l.toString()
                                  + " due to sensor disconnection");
                      }
                      cancelTriggerSensorImpl(l, sensor, true);
                  }
              }
          } else {
              synchronized (mSensorListeners) {
                  HashMap<SensorEventListener, SensorEventQueue> sensorListeners =
                          new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
  
                  for (SensorEventListener l: sensorListeners.keySet()) {
                      if (DEBUG_DYNAMIC_SENSOR) {
                          Log.i(TAG, "removed event listener" + l.toString()
                                  + " due to sensor disconnection");
                      }
                      unregisterListenerImpl(l, sensor);
                  }
              }
          }
      }
  
      private void updateDynamicSensorList() {
          synchronized (mFullDynamicSensorsList) {
              if (mDynamicSensorListDirty) {
                  List<Sensor> list = new ArrayList<>();
                  nativeGetDynamicSensors(mNativeInstance, list);
  
                  final List<Sensor> updatedList = new ArrayList<>();
                  final List<Sensor> addedList = new ArrayList<>();
                  final List<Sensor> removedList = new ArrayList<>();
  
                  boolean changed = diffSortedSensorList(
                          mFullDynamicSensorsList, list, updatedList, addedList, removedList);
  
                  if (changed) {
                      if (DEBUG_DYNAMIC_SENSOR) {
                          Log.i(TAG, "DYNS dynamic sensor list cached should be updated");
                      }
                      mFullDynamicSensorsList = updatedList;
  
                      for (Sensor s: addedList) {
                          mHandleToSensor.put(s.getHandle(), s);
                      }
  
                      Handler mainHandler = new Handler(mContext.getMainLooper());
  
                      for (Map.Entry<DynamicSensorCallback, Handler> entry :
                              mDynamicSensorCallbacks.entrySet()) {
                          final DynamicSensorCallback callback = entry.getKey();
                          Handler handler =
                                  entry.getValue() == null ? mainHandler : entry.getValue();
  
                          handler.post(new Runnable() {
                              @Override
                              public void run() {
                                  for (Sensor s: addedList) {
                                      callback.onDynamicSensorConnected(s);
                                  }
                                  for (Sensor s: removedList) {
                                      callback.onDynamicSensorDisconnected(s);
                                  }
                              }
                          });
                      }
  
                      for (Sensor s: removedList) {
                          cleanupSensorConnection(s);
                      }
                  }
  
                  mDynamicSensorListDirty = false;
              }
          }
      }
  
      private void setupDynamicSensorBroadcastReceiver() {
          if (mDynamicSensorBroadcastReceiver == null) {
              mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
                  @Override
                  public void onReceive(Context context, Intent intent) {
                      if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
                          if (DEBUG_DYNAMIC_SENSOR) {
                              Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
                          }
                          // Dynamic sensors probably changed
                          mDynamicSensorListDirty = true;
                          updateDynamicSensorList();
                      }
                  }
              };
  
              IntentFilter filter = new IntentFilter("dynamic_sensor_change");
              filter.addAction(Intent.ACTION_DYNAMIC_SENSOR_CHANGED);
              mContext.registerReceiver(mDynamicSensorBroadcastReceiver, filter);
          }
      }
  
      private void teardownDynamicSensorBroadcastReceiver() {
          mDynamicSensorCallbacks.clear();
          mContext.unregisterReceiver(mDynamicSensorBroadcastReceiver);
          mDynamicSensorBroadcastReceiver = null;
      }
  
      /** @hide */
      protected void registerDynamicSensorCallbackImpl(
              DynamicSensorCallback callback, Handler handler) {
          if (DEBUG_DYNAMIC_SENSOR) {
              Log.i(TAG, "DYNS Register dynamic sensor callback");
          }
  
          if (callback == null) {
              throw new IllegalArgumentException("callback cannot be null");
          }
          if (mDynamicSensorCallbacks.containsKey(callback)) {
              // has been already registered, ignore
              return;
          }
  
          setupDynamicSensorBroadcastReceiver();
          mDynamicSensorCallbacks.put(callback, handler);
      }
  
      /** @hide */
      protected void unregisterDynamicSensorCallbackImpl(
              DynamicSensorCallback callback) {
          if (DEBUG_DYNAMIC_SENSOR) {
              Log.i(TAG, "Removing dynamic sensor listerner");
          }
          mDynamicSensorCallbacks.remove(callback);
      }
  
      /*
       * Find the difference of two List<Sensor> assuming List are sorted by handle of sensor,
       * assuming the input list is already sorted by handle. Inputs are ol and nl; outputs are
       * updated, added and removed. Any of the output lists can be null in case the result is not
       * interested.
       */
      private static boolean diffSortedSensorList(
              List<Sensor> oldList, List<Sensor> newList, List<Sensor> updated,
              List<Sensor> added, List<Sensor> removed) {
  
          boolean changed = false;
  
          int i = 0, j = 0;
          while (true) {
              if (j < oldList.size() && (i >= newList.size()
                      || newList.get(i).getHandle() > oldList.get(j).getHandle())) {
                  changed = true;
                  if (removed != null) {
                      removed.add(oldList.get(j));
                  }
                  ++j;
              } else if (i < newList.size() && (j >= oldList.size()
                      || newList.get(i).getHandle() < oldList.get(j).getHandle())) {
                  changed = true;
                  if (added != null) {
                      added.add(newList.get(i));
                  }
                  if (updated != null) {
                      updated.add(newList.get(i));
                  }
                  ++i;
              } else if (i < newList.size() && j < oldList.size()
                      && newList.get(i).getHandle() == oldList.get(j).getHandle()) {
                  if (updated != null) {
                      updated.add(oldList.get(j));
                  }
                  ++i;
                  ++j;
              } else {
                  break;
              }
          }
          return changed;
      }
  
      /** @hide */
      protected int configureDirectChannelImpl(
              SensorDirectChannel channel, Sensor sensor, int rate) {
          if (!channel.isOpen()) {
              throw new IllegalStateException("channel is closed");
          }
  
          if (rate < SensorDirectChannel.RATE_STOP
                  || rate > SensorDirectChannel.RATE_VERY_FAST) {
              throw new IllegalArgumentException("rate parameter invalid");
          }
  
          if (sensor == null && rate != SensorDirectChannel.RATE_STOP) {
              // the stop all sensors case
              throw new IllegalArgumentException(
                      "when sensor is null, rate can only be DIRECT_RATE_STOP");
          }
  
          int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
  
          int ret = nativeConfigDirectChannel(
                  mNativeInstance, channel.getNativeHandle(), sensorHandle, rate);
  
          if (rate == SensorDirectChannel.RATE_STOP) {
              return (ret == 0) ? 1 : 0;
          } else {
              return (ret > 0) ? ret : 0;
          }
      }
  
      /** @hide */
      protected SensorDirectChannel createDirectChannelImpl(
              MemoryFile memoryFile, HardwareBuffer hardwareBuffer) {
          int id;
          int type;
          long size;
          if (memoryFile != null) {
              int fd;
              try {
                  fd = memoryFile.getFileDescriptor().getInt$();
              } catch (IOException e) {
                  throw new IllegalArgumentException("MemoryFile object is not valid");
              }
  
              if (memoryFile.length() < MIN_DIRECT_CHANNEL_BUFFER_SIZE) {
                  throw new IllegalArgumentException(
                          "Size of MemoryFile has to be greater than "
                          + MIN_DIRECT_CHANNEL_BUFFER_SIZE);
              }
  
              size = memoryFile.length();
              id = nativeCreateDirectChannel(
                      mNativeInstance, size, SensorDirectChannel.TYPE_MEMORY_FILE, fd, null);
              if (id <= 0) {
                  throw new UncheckedIOException(
                          new IOException("create MemoryFile direct channel failed " + id));
              }
              type = SensorDirectChannel.TYPE_MEMORY_FILE;
          } else if (hardwareBuffer != null) {
              if (hardwareBuffer.getFormat() != HardwareBuffer.BLOB) {
                  throw new IllegalArgumentException("Format of HardwareBuffer must be BLOB");
              }
              if (hardwareBuffer.getHeight() != 1) {
                  throw new IllegalArgumentException("Height of HardwareBuffer must be 1");
              }
              if (hardwareBuffer.getWidth() < MIN_DIRECT_CHANNEL_BUFFER_SIZE) {
                  throw new IllegalArgumentException(
                          "Width if HaradwareBuffer must be greater than "
                          + MIN_DIRECT_CHANNEL_BUFFER_SIZE);
              }
              if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_SENSOR_DIRECT_DATA) == 0) {
                  throw new IllegalArgumentException(
                          "HardwareBuffer must set usage flag USAGE_SENSOR_DIRECT_DATA");
              }
              size = hardwareBuffer.getWidth();
              id = nativeCreateDirectChannel(
                      mNativeInstance, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER,
                      -1, hardwareBuffer);
              if (id <= 0) {
                  throw new UncheckedIOException(
                          new IOException("create HardwareBuffer direct channel failed " + id));
              }
              type = SensorDirectChannel.TYPE_HARDWARE_BUFFER;
          } else {
              throw new NullPointerException("shared memory object cannot be null");
          }
          return new SensorDirectChannel(this, id, type, size);
      }
  
      /** @hide */
      protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
          if (channel != null) {
              nativeDestroyDirectChannel(mNativeInstance, channel.getNativeHandle());
          }
      }
  
      /*
       * BaseEventQueue is the communication channel with the sensor service,
       * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
       * the queues and the listeners. InjectEventQueue is also a sub-class which is a special case
       * where data is being injected into the sensor HAL through the sensor service. It is not
       * associated with any listener and there is one InjectEventQueue associated with a
       * SensorManager instance.
       */
      private abstract static class BaseEventQueue {
          private static native long nativeInitBaseEventQueue(long nativeManager,
                  WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ,
                  String packageName, int mode, String opPackageName);
          private static native int nativeEnableSensor(long eventQ, int handle, int rateUs,
                  int maxBatchReportLatencyUs);
          private static native int nativeDisableSensor(long eventQ, int handle);
          private static native void nativeDestroySensorEventQueue(long eventQ);
          private static native int nativeFlushSensor(long eventQ);
          private static native int nativeInjectSensorData(long eventQ, int handle,
                  float[] values, int accuracy, long timestamp);
  
          private long mNativeSensorEventQueue;
          private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
          protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
          private final CloseGuard mCloseGuard = CloseGuard.get();
          protected final SystemSensorManager mManager;
  
          protected static final int OPERATING_MODE_NORMAL = 0;
          protected static final int OPERATING_MODE_DATA_INJECTION = 1;
  
          BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
              if (packageName == null) packageName = "";
              mNativeSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
                      new WeakReference<>(this), looper.getQueue(),
                      packageName, mode, manager.mContext.getOpPackageName());
              mCloseGuard.open("dispose");
              mManager = manager;
          }
  
          public void dispose() {
              dispose(false);
          }
  
          public boolean addSensor(
                  Sensor sensor, int delayUs, int maxBatchReportLatencyUs) {
              // Check if already present.
              int handle = sensor.getHandle();
              if (mActiveSensors.get(handle)) return false;
  
              // Get ready to receive events before calling enable.
              mActiveSensors.put(handle, true);
              addSensorEvent(sensor);
              if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
                  // Try continuous mode if batching fails.
                  if (maxBatchReportLatencyUs == 0
                          || maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
                      removeSensor(sensor, false);
                      return false;
                  }
              }
              return true;
          }
  
          public boolean removeAllSensors() {
              for (int i = 0; i < mActiveSensors.size(); i++) {
                  if (mActiveSensors.valueAt(i) == true) {
                      int handle = mActiveSensors.keyAt(i);
                      Sensor sensor = mManager.mHandleToSensor.get(handle);
                      if (sensor != null) {
                          disableSensor(sensor);
                          mActiveSensors.put(handle, false);
                          removeSensorEvent(sensor);
                      } else {
                          // sensor just disconnected -- just ignore.
                      }
                  }
              }
              return true;
          }
  
          public boolean removeSensor(Sensor sensor, boolean disable) {
              final int handle = sensor.getHandle();
              if (mActiveSensors.get(handle)) {
                  if (disable) disableSensor(sensor);
                  mActiveSensors.put(sensor.getHandle(), false);
                  removeSensorEvent(sensor);
                  return true;
              }
              return false;
          }
  
          public int flush() {
              if (mNativeSensorEventQueue == 0) throw new NullPointerException();
              return nativeFlushSensor(mNativeSensorEventQueue);
          }
  
          public boolean hasSensors() {
              // no more sensors are set
              return mActiveSensors.indexOfValue(true) >= 0;
          }
  
          @Override
          protected void finalize() throws Throwable {
              try {
                  dispose(true);
              } finally {
                  super.finalize();
              }
          }
  
          private void dispose(boolean finalized) {
              if (mCloseGuard != null) {
                  if (finalized) {
                      mCloseGuard.warnIfOpen();
                  }
                  mCloseGuard.close();
              }
              if (mNativeSensorEventQueue != 0) {
                  nativeDestroySensorEventQueue(mNativeSensorEventQueue);
                  mNativeSensorEventQueue = 0;
              }
          }
  
          private int enableSensor(
                  Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
              if (mNativeSensorEventQueue == 0) throw new NullPointerException();
              if (sensor == null) throw new NullPointerException();
              return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
                      maxBatchReportLatencyUs);
          }
  
          protected int injectSensorDataBase(int handle, float[] values, int accuracy,
                                             long timestamp) {
              return nativeInjectSensorData(
                      mNativeSensorEventQueue, handle, values, accuracy, timestamp);
          }
  
          private int disableSensor(Sensor sensor) {
              if (mNativeSensorEventQueue == 0) throw new NullPointerException();
              if (sensor == null) throw new NullPointerException();
              return nativeDisableSensor(mNativeSensorEventQueue, sensor.getHandle());
          }
          @UnsupportedAppUsage
          protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
                  long timestamp);
          @UnsupportedAppUsage
          protected abstract void dispatchFlushCompleteEvent(int handle);
  
          @UnsupportedAppUsage
          protected void dispatchAdditionalInfoEvent(
                  int handle, int type, int serial, float[] floatValues, int[] intValues) {
              // default implementation is do nothing
          }
  
          protected abstract void addSensorEvent(Sensor sensor);
          protected abstract void removeSensorEvent(Sensor sensor);
      }
  
      static final class SensorEventQueue extends BaseEventQueue {
          private final SensorEventListener mListener;
          private final SparseArray<SensorEvent> mSensorsEvents = new SparseArray<SensorEvent>();
  
          public SensorEventQueue(SensorEventListener listener, Looper looper,
                  SystemSensorManager manager, String packageName) {
              super(looper, manager, OPERATING_MODE_NORMAL, packageName);
              mListener = listener;
          }
  
          @Override
          public void addSensorEvent(Sensor sensor) {
              SensorEvent t = new SensorEvent(Sensor.getMaxLengthValuesArray(sensor,
                      mManager.mTargetSdkLevel));
              synchronized (mSensorsEvents) {
                  mSensorsEvents.put(sensor.getHandle(), t);
              }
          }
  
          @Override
          public void removeSensorEvent(Sensor sensor) {
              synchronized (mSensorsEvents) {
                  mSensorsEvents.delete(sensor.getHandle());
              }
          }
  
          // Called from native code.
          @SuppressWarnings("unused")
          @Override
          protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,
                  long timestamp) {
              final Sensor sensor = mManager.mHandleToSensor.get(handle);
              if (sensor == null) {
                  // sensor disconnected
                  return;
              }
  
              SensorEvent t = null;
              synchronized (mSensorsEvents) {
                  t = mSensorsEvents.get(handle);
              }
  
              if (t == null) {
                  // This may happen if the client has unregistered and there are pending events in
                  // the queue waiting to be delivered. Ignore.
                  return;
              }
              // Copy from the values array.
              System.arraycopy(values, 0, t.values, 0, t.values.length);
              t.timestamp = timestamp;
              t.accuracy = inAccuracy;
              t.sensor = sensor;
  
              // call onAccuracyChanged() only if the value changes
              final int accuracy = mSensorAccuracies.get(handle);
              if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
                  mSensorAccuracies.put(handle, t.accuracy);
                  mListener.onAccuracyChanged(t.sensor, t.accuracy);
              }
              mListener.onSensorChanged(t);
          }
  
          // Called from native code.
          @SuppressWarnings("unused")
          @Override
          protected void dispatchFlushCompleteEvent(int handle) {
              if (mListener instanceof SensorEventListener2) {
                  final Sensor sensor = mManager.mHandleToSensor.get(handle);
                  if (sensor == null) {
                      // sensor disconnected
                      return;
                  }
                  ((SensorEventListener2) mListener).onFlushCompleted(sensor);
              }
              return;
          }
  
          // Called from native code.
          @SuppressWarnings("unused")
          @Override
          protected void dispatchAdditionalInfoEvent(
                  int handle, int type, int serial, float[] floatValues, int[] intValues) {
              if (mListener instanceof SensorEventCallback) {
                  final Sensor sensor = mManager.mHandleToSensor.get(handle);
                  if (sensor == null) {
                      // sensor disconnected
                      return;
                  }
                  SensorAdditionalInfo info =
                          new SensorAdditionalInfo(sensor, type, serial, intValues, floatValues);
                  ((SensorEventCallback) mListener).onSensorAdditionalInfo(info);
              }
          }
      }
  
      static final class TriggerEventQueue extends BaseEventQueue {
          private final TriggerEventListener mListener;
          private final SparseArray<TriggerEvent> mTriggerEvents = new SparseArray<TriggerEvent>();
  
          public TriggerEventQueue(TriggerEventListener listener, Looper looper,
                  SystemSensorManager manager, String packageName) {
              super(looper, manager, OPERATING_MODE_NORMAL, packageName);
              mListener = listener;
          }
  
          @Override
          public void addSensorEvent(Sensor sensor) {
              TriggerEvent t = new TriggerEvent(Sensor.getMaxLengthValuesArray(sensor,
                      mManager.mTargetSdkLevel));
              synchronized (mTriggerEvents) {
                  mTriggerEvents.put(sensor.getHandle(), t);
              }
          }
  
          @Override
          public void removeSensorEvent(Sensor sensor) {
              synchronized (mTriggerEvents) {
                  mTriggerEvents.delete(sensor.getHandle());
              }
          }
  
          // Called from native code.
          @SuppressWarnings("unused")
          @Override
          protected void dispatchSensorEvent(int handle, float[] values, int accuracy,
                  long timestamp) {
              final Sensor sensor = mManager.mHandleToSensor.get(handle);
              if (sensor == null) {
                  // sensor disconnected
                  return;
              }
              TriggerEvent t = null;
              synchronized (mTriggerEvents) {
                  t = mTriggerEvents.get(handle);
              }
              if (t == null) {
                  Log.e(TAG, "Error: Trigger Event is null for Sensor: " + sensor);
                  return;
              }
  
              // Copy from the values array.
              System.arraycopy(values, 0, t.values, 0, t.values.length);
              t.timestamp = timestamp;
              t.sensor = sensor;
  
              // A trigger sensor is auto disabled. So just clean up and don't call native
              // disable.
              mManager.cancelTriggerSensorImpl(mListener, sensor, false);
  
              mListener.onTrigger(t);
          }
  
          @SuppressWarnings("unused")
          protected void dispatchFlushCompleteEvent(int handle) {
          }
      }
  
      final class InjectEventQueue extends BaseEventQueue {
          public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) {
              super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
          }
  
          int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
              return injectSensorDataBase(handle, values, accuracy, timestamp);
          }
  
          @SuppressWarnings("unused")
          protected void dispatchSensorEvent(int handle, float[] values, int accuracy,
                  long timestamp) {
          }
  
          @SuppressWarnings("unused")
          protected void dispatchFlushCompleteEvent(int handle) {
  
          }
  
          @SuppressWarnings("unused")
          protected void addSensorEvent(Sensor sensor) {
  
          }
  
          @SuppressWarnings("unused")
          protected void removeSensorEvent(Sensor sensor) {
  
          }
      }
  
      protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
          int handle = -1;
          if (parameter.sensor != null) handle = parameter.sensor.getHandle();
          return nativeSetOperationParameter(
                  mNativeInstance, handle,
                  parameter.type, parameter.floatValues, parameter.intValues) == 0;
      }
  }
  