Dynamic Sensor Discovery Implementation

Defined the dynamic sensor meta data type and UUID of sensor, as well
as the broadcast intent that is related to dynamic sensor status
change.

Modify SensorManager, SystemSensorManager and the JNI layer to support
dynamic sensor discovery.

Added SensorNotificationService to notify apps that sensor connected
or disconnected via broadcast.

Change-Id: I28adfb005357d7e3983d65117d6865830c75027d
diff --git a/api/current.txt b/api/current.txt
index 7d3a94c..d6e00b0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13358,6 +13358,7 @@
     method public float getResolution();
     method public java.lang.String getStringType();
     method public int getType();
+    method public java.util.UUID getUuid();
     method public java.lang.String getVendor();
     method public int getVersion();
     method public boolean isWakeUpSensor();
@@ -13446,6 +13447,7 @@
     method public static void getAngleChange(float[], float[], float[]);
     method public android.hardware.Sensor getDefaultSensor(int);
     method public android.hardware.Sensor getDefaultSensor(int, boolean);
+    method public java.util.List<android.hardware.Sensor> getDynamicSensorList(int);
     method public static float getInclination(float[]);
     method public static float[] getOrientation(float[], float[]);
     method public static void getQuaternionFromVector(float[], float[]);
@@ -13453,6 +13455,8 @@
     method public static void getRotationMatrixFromVector(float[], float[]);
     method public java.util.List<android.hardware.Sensor> getSensorList(int);
     method public deprecated int getSensors();
+    method public void registerDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback);
+    method public void registerDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback, android.os.Handler);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
@@ -13461,6 +13465,7 @@
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, android.os.Handler);
     method public static boolean remapCoordinateSystem(float[], int, int, float[]);
     method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+    method public void unregisterDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback);
     method public deprecated void unregisterListener(android.hardware.SensorListener);
     method public deprecated void unregisterListener(android.hardware.SensorListener, int);
     method public void unregisterListener(android.hardware.SensorEventListener, android.hardware.Sensor);
@@ -13525,6 +13530,12 @@
     field public static final float STANDARD_GRAVITY = 9.80665f;
   }
 
+  public static abstract class SensorManager.DynamicSensorConnectionCallback {
+    ctor public SensorManager.DynamicSensorConnectionCallback();
+    method public void onDynamicSensorConnected(android.hardware.Sensor);
+    method public void onDynamicSensorDisconnected(android.hardware.Sensor);
+  }
+
   public final class TriggerEvent {
     field public android.hardware.Sensor sensor;
     field public long timestamp;
diff --git a/api/system-current.txt b/api/system-current.txt
index 6eee50b..47e57fa 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -13758,6 +13758,7 @@
     method public float getResolution();
     method public java.lang.String getStringType();
     method public int getType();
+    method public java.util.UUID getUuid();
     method public java.lang.String getVendor();
     method public int getVersion();
     method public boolean isDataInjectionSupported();
@@ -13768,6 +13769,7 @@
     field public static final int REPORTING_MODE_SPECIAL_TRIGGER = 3; // 0x3
     field public static final java.lang.String STRING_TYPE_ACCELEROMETER = "android.sensor.accelerometer";
     field public static final java.lang.String STRING_TYPE_AMBIENT_TEMPERATURE = "android.sensor.ambient_temperature";
+    field public static final java.lang.String STRING_TYPE_DYNAMIC_SENSOR_META = "android.sensor.dynamic_sensor_meta";
     field public static final java.lang.String STRING_TYPE_GAME_ROTATION_VECTOR = "android.sensor.game_rotation_vector";
     field public static final java.lang.String STRING_TYPE_GEOMAGNETIC_ROTATION_VECTOR = "android.sensor.geomagnetic_rotation_vector";
     field public static final java.lang.String STRING_TYPE_GRAVITY = "android.sensor.gravity";
@@ -13795,6 +13797,7 @@
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
+    field public static final int TYPE_DYNAMIC_SENSOR_META = 32; // 0x20
     field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
     field public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; // 0x14
     field public static final int TYPE_GRAVITY = 9; // 0x9
@@ -13849,6 +13852,7 @@
     method public static void getAngleChange(float[], float[], float[]);
     method public android.hardware.Sensor getDefaultSensor(int);
     method public android.hardware.Sensor getDefaultSensor(int, boolean);
+    method public java.util.List<android.hardware.Sensor> getDynamicSensorList(int);
     method public static float getInclination(float[]);
     method public static float[] getOrientation(float[], float[]);
     method public static void getQuaternionFromVector(float[], float[]);
@@ -13858,6 +13862,8 @@
     method public deprecated int getSensors();
     method public boolean initDataInjection(boolean);
     method public boolean injectSensorData(android.hardware.Sensor, float[], int, long);
+    method public void registerDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback);
+    method public void registerDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback, android.os.Handler);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
@@ -13866,6 +13872,7 @@
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, android.os.Handler);
     method public static boolean remapCoordinateSystem(float[], int, int, float[]);
     method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+    method public void unregisterDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback);
     method public deprecated void unregisterListener(android.hardware.SensorListener);
     method public deprecated void unregisterListener(android.hardware.SensorListener, int);
     method public void unregisterListener(android.hardware.SensorEventListener, android.hardware.Sensor);
@@ -13930,6 +13937,12 @@
     field public static final float STANDARD_GRAVITY = 9.80665f;
   }
 
+  public static abstract class SensorManager.DynamicSensorConnectionCallback {
+    ctor public SensorManager.DynamicSensorConnectionCallback();
+    method public void onDynamicSensorConnected(android.hardware.Sensor);
+    method public void onDynamicSensorDisconnected(android.hardware.Sensor);
+  }
+
   public final class TriggerEvent {
     field public android.hardware.Sensor sensor;
     field public long timestamp;
diff --git a/api/test-current.txt b/api/test-current.txt
index f4a873e..640709f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13366,6 +13366,7 @@
     method public float getResolution();
     method public java.lang.String getStringType();
     method public int getType();
+    method public java.util.UUID getUuid();
     method public java.lang.String getVendor();
     method public int getVersion();
     method public boolean isWakeUpSensor();
@@ -13454,6 +13455,7 @@
     method public static void getAngleChange(float[], float[], float[]);
     method public android.hardware.Sensor getDefaultSensor(int);
     method public android.hardware.Sensor getDefaultSensor(int, boolean);
+    method public java.util.List<android.hardware.Sensor> getDynamicSensorList(int);
     method public static float getInclination(float[]);
     method public static float[] getOrientation(float[], float[]);
     method public static void getQuaternionFromVector(float[], float[]);
@@ -13461,6 +13463,8 @@
     method public static void getRotationMatrixFromVector(float[], float[]);
     method public java.util.List<android.hardware.Sensor> getSensorList(int);
     method public deprecated int getSensors();
+    method public void registerDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback);
+    method public void registerDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback, android.os.Handler);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
@@ -13469,6 +13473,7 @@
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, android.os.Handler);
     method public static boolean remapCoordinateSystem(float[], int, int, float[]);
     method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+    method public void unregisterDynamicSensorCallback(android.hardware.SensorManager.DynamicSensorConnectionCallback);
     method public deprecated void unregisterListener(android.hardware.SensorListener);
     method public deprecated void unregisterListener(android.hardware.SensorListener, int);
     method public void unregisterListener(android.hardware.SensorEventListener, android.hardware.Sensor);
@@ -13533,6 +13538,12 @@
     field public static final float STANDARD_GRAVITY = 9.80665f;
   }
 
+  public static abstract class SensorManager.DynamicSensorConnectionCallback {
+    ctor public SensorManager.DynamicSensorConnectionCallback();
+    method public void onDynamicSensorConnected(android.hardware.Sensor);
+    method public void onDynamicSensorDisconnected(android.hardware.Sensor);
+  }
+
   public final class TriggerEvent {
     field public android.hardware.Sensor sensor;
     field public long timestamp;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 426dafb..158a284 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2618,8 +2618,7 @@
      *   turned off</li>
      * </ul>
      *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE";
@@ -3210,6 +3209,18 @@
     public static final String
             ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
 
+    /**
+     * Broadcast Action: List of dynamic sensor is changed due to new sensor being connected or
+     * exisiting sensor being disconnected.
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     *
+     * {@hide}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String
+            ACTION_DYNAMIC_SENSOR_CHANGED = "android.intent.action.DYNAMIC_SENSOR_CHANGED";
+
     /** {@hide} */
     public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
 
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index c710f21..841e5b0 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -17,8 +17,10 @@
 
 package android.hardware;
 
-import android.os.Build;
 import android.annotation.SystemApi;
+import android.os.Build;
+
+import java.util.UUID;
 
 /**
  * Class representing a sensor. Use {@link SensorManager#getSensorList} to get
@@ -622,6 +624,29 @@
 
     public static final String STRING_TYPE_HEART_BEAT = "android.sensor.heart_beat";
     /**
+     * A constant describing a dynamic sensor meta event sensor.
+     *
+     * A sensor event of this type is received when a dynamic sensor is added to or removed from
+     * the system. This sensor type should always use special trigger report mode ({@code
+     * SensorManager.REPORTING_MODE_SPECIAL_TRIGGER}).
+     *
+     * @hide This sensor is expected to be used only by system services.
+     */
+    @SystemApi
+    public static final int TYPE_DYNAMIC_SENSOR_META = 32;
+
+    /**
+     * A constant string describing a dynamic sensor meta event sensor.
+     *
+     * @see #TYPE_DYNAMIC_SENSOR_META
+     *
+     * @hide This sensor is expected to only be used by the system service
+     */
+    @SystemApi
+    public static final String STRING_TYPE_DYNAMIC_SENSOR_META =
+            "android.sensor.dynamic_sensor_meta";
+
+    /**
      * A constant describing all sensor types.
      */
 
@@ -708,7 +733,11 @@
             1, // SENSOR_TYPE_PICK_UP_GESTURE
             1, // SENSOR_TYPE_WRIST_TILT_GESTURE
             1, // SENSOR_TYPE_DEVICE_ORIENTATION
-            16, // SENSOR_TYPE_POSE_6DOF
+            16,// SENSOR_TYPE_POSE_6DOF
+            1, // SENSOR_TYPE_STATIONARY_DETECT
+            1, // SENSOR_TYPE_MOTION_DETECT
+            1, // SENSOR_TYPE_HEART_BEAT
+            2, // SENSOR_TYPE_DYNAMIC_SENSOR_META
     };
 
     /**
@@ -734,12 +763,10 @@
         }
         int offset = sensor.mType;
         if (offset >= sSensorReportingModes.length) {
-            // we don't know about this sensor, so this is probably a
-            // vendor-defined sensor, in that case, we don't know how many value
-            // it has
-            // so we return the maximum and assume the app will know.
-            // FIXME: sensor HAL should advertise how much data is returned per
-            // sensor
+            // we don't know about this sensor, so this is probably a vendor-defined sensor, in that
+            // case, we don't know how many value it has so we return the maximum and assume the app
+            // will know.
+            // FIXME: sensor HAL should advertise how much data is returned per sensor
             return 16;
         }
         return sSensorReportingModes[offset];
@@ -763,6 +790,7 @@
     private String  mRequiredPermission;
     private int     mMaxDelay;
     private int     mFlags;
+    private UUID    mUuid;
 
     Sensor() {
     }
@@ -851,6 +879,13 @@
     }
 
     /**
+     * @return The type of this sensor as a string.
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
      * @hide
      * @return The permission required to access this sensor. If empty, no permission is required.
      */
@@ -1033,6 +1068,9 @@
             case TYPE_DEVICE_ORIENTATION:
                 mStringType = STRING_TYPE_DEVICE_ORIENTATION;
                 return true;
+            case TYPE_DYNAMIC_SENSOR_META:
+                mStringType = STRING_TYPE_DYNAMIC_SENSOR_META;
+                return true;
             default:
                 return false;
         }
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 5d405f9..f0b17c30 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -377,6 +377,12 @@
     protected abstract List<Sensor> getFullSensorList();
 
     /**
+     * Gets the full list of dynamic sensors that are available.
+     * @hide
+     */
+    protected abstract List<Sensor> getFullDynamicSensorList();
+
+    /**
      * @return available sensors.
      * @deprecated This method is deprecated, use
      *             {@link SensorManager#getSensorList(int)} instead
@@ -430,6 +436,38 @@
     }
 
     /**
+     * Use this method to get a list of available dynamic sensors of a certain type.
+     * Make multiple calls to get sensors of different types or use
+     * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all dynamic sensors.
+     *
+     * <p class="note">
+     * NOTE: Both wake-up and non wake-up sensors matching the given type are
+     * returned. Check {@link Sensor#isWakeUpSensor()} to know the wake-up properties
+     * of the returned {@link Sensor}.
+     * </p>
+     *
+     * @param type of sensors requested
+     *
+     * @return a list of dynamic sensors matching the requested type.
+     *
+     * @see Sensor
+     */
+    public List<Sensor> getDynamicSensorList(int type) {
+        // cache the returned lists the first time
+        final List<Sensor> fullList = getFullDynamicSensorList();
+        if (type == Sensor.TYPE_ALL) {
+            return Collections.unmodifiableList(fullList);
+        } else {
+            List<Sensor> list = new ArrayList();
+            for (Sensor i : fullList) {
+                if (i.getType() == type)
+                    list.add(i);
+            }
+            return Collections.unmodifiableList(list);
+        }
+    }
+
+    /**
      * Use this method to get the default sensor for a given type. Note that the
      * returned sensor could be a composite sensor, and its data could be
      * averaged or filtered. If you need to access the raw sensors use
@@ -841,6 +879,86 @@
     /** @hide */
     protected abstract boolean flushImpl(SensorEventListener listener);
 
+
+    /**
+     * Used for receiving notifications from the SensorManager when dynamic sensors are connected or
+     * disconnected.
+     */
+    public static abstract class DynamicSensorConnectionCallback {
+        /**
+         * Called when there is a dynamic sensor being connected to the system.
+         *
+         * @param sensor the newly connected sensor. See {@link android.hardware.Sensor Sensor}.
+         */
+        public void onDynamicSensorConnected(Sensor sensor) {}
+
+        /**
+         * Called when there is a dynamic sensor being disconnected from the system.
+         *
+         * @param sensor the disconnected sensor. See {@link android.hardware.Sensor Sensor}.
+         */
+        public void onDynamicSensorDisconnected(Sensor sensor) {}
+    }
+
+
+    /**
+     * Add a {@link android.hardware.SensorManager.DynamicSensorConnectionCallback
+     * DynamicSensorConnectionCallback} to receive dynamic sensor connection callbacks. Repeat
+     * registration with the already registered callback object will have no additional effect.
+     *
+     * @param callback An object that implements the
+     *        {@link android.hardware.SensorManager.DynamicSensorConnectionCallback
+     *        DynamicSensorConnectionCallback}
+     *        interface for receiving callbacks.
+     * @see #addDynamicSensorCallback(DynamicSensorConnectionCallback, Handler)
+     *
+     * @throws IllegalArgumentException when callback is null.
+     */
+    public void registerDynamicSensorCallback(DynamicSensorConnectionCallback callback) {
+        registerDynamicSensorCallback(callback, null);
+    }
+
+    /**
+     * Add a {@link android.hardware.SensorManager.DynamicSensorConnectionCallback
+     * DynamicSensorConnectionCallback} to receive dynamic sensor connection callbacks. Repeat
+     * registration with the already registered callback object will have no additional effect.
+     *
+     * @param callback An object that implements the
+     *        {@link android.hardware.SensorManager.DynamicSensorConnectionCallback
+     *        DynamicSensorConnectionCallback} interface for receiving callbacks.
+     * @param handler The {@link android.os.Handler Handler} the {@link
+     *        android.hardware.SensorManager.DynamicSensorConnectionCallback
+     *        sensor connection events} will be delivered to.
+     *
+     * @throws IllegalArgumentException when callback is null.
+     */
+    public void registerDynamicSensorCallback(
+            DynamicSensorConnectionCallback callback, Handler handler) {
+        registerDynamicSensorCallbackImpl(callback, handler);
+    }
+
+    /**
+     * Remove a {@link android.hardware.SensorManager.DynamicSensorConnectionCallback
+     * DynamicSensorConnectionCallback} to stop sending dynamic sensor connection events to that
+     * callback.
+     *
+     * @param callback An object that implements the
+     *        {@link android.hardware.SensorManager.DynamicSensorConnectionCallback
+     *        DynamicSensorConnectionCallback}
+     *        interface for receiving callbacks.
+     */
+    public void unregisterDynamicSensorCallback(DynamicSensorConnectionCallback callback) {
+        unregisterDynamicSensorCallbackImpl(callback);
+    }
+
+    /** @hide */
+    protected abstract void registerDynamicSensorCallbackImpl(
+            DynamicSensorConnectionCallback callback, Handler handler);
+
+    /** @hide */
+    protected abstract void unregisterDynamicSensorCallbackImpl(
+            DynamicSensorConnectionCallback callback);
+
     /**
      * <p>
      * Computes the inclination matrix <b>I</b> as well as the rotation matrix
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 2fe8fb6..e91195c 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -17,7 +17,10 @@
 package android.hardware;
 
 import android.Manifest;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -32,6 +35,7 @@
 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
@@ -40,10 +44,14 @@
  * @hide
  */
 public class SystemSensorManager extends SensorManager {
+    //TODO: disable extra logging before release
+    private static boolean DEBUG_DYNAMIC_SENSOR = true;
+
     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 boolean sSensorModuleInitialized = false;
@@ -52,7 +60,10 @@
     private final Object mLock = new Object();
 
     private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
-    private final SparseArray<Sensor> mHandleToSensor = new SparseArray<>();
+    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 =
@@ -60,6 +71,11 @@
     private final HashMap<TriggerEventListener, TriggerEventQueue> mTriggerListeners =
             new HashMap<TriggerEventListener, TriggerEventQueue>();
 
+    // Dynamic Sensor callbacks
+    private HashMap<DynamicSensorConnectionCallback, 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;
@@ -84,7 +100,7 @@
                 Sensor sensor = new Sensor();
                 if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
                 mFullSensorsList.add(sensor);
-                mHandleToSensor.append(sensor.getHandle(), sensor);
+                mHandleToSensor.put(sensor.getHandle(), sensor);
             }
         }
     }
@@ -96,6 +112,15 @@
         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 DynamicSensorConnectionCallback
+        setupDynamicSensorBroadcastReceiver();
+        updateDynamicSensorList();
+        return mFullDynamicSensorsList;
+    }
 
     /** @hide */
     @Override
@@ -274,6 +299,187 @@
         }
     }
 
+    private void cleanupSensorConnection(Sensor sensor) {
+        mHandleToSensor.remove(sensor.getHandle());
+
+        if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
+            synchronized(mTriggerListeners) {
+                for (TriggerEventListener l: mTriggerListeners.keySet()) {
+                    if (DEBUG_DYNAMIC_SENSOR){
+                        Log.i(TAG, "removed trigger listener" + l.toString() +
+                                   " due to sensor disconnection");
+                    }
+                    cancelTriggerSensorImpl(l, sensor, true);
+                }
+            }
+        } else {
+            synchronized(mSensorListeners) {
+                for (SensorEventListener l: mSensorListeners.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<DynamicSensorConnectionCallback, Handler> entry :
+                            mDynamicSensorCallbacks.entrySet()) {
+                        final DynamicSensorConnectionCallback 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(
+            DynamicSensorConnectionCallback 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(
+            DynamicSensorConnectionCallback 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;
+    }
+
     /*
      * BaseEventQueue is the communication channel with the sensor service,
      * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
@@ -297,7 +503,6 @@
         private long nSensorEventQueue;
         private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
         protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
-        protected final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private final float[] mScratch = new float[16];
         protected final SystemSensorManager mManager;
@@ -348,7 +553,7 @@
                         mActiveSensors.put(handle, false);
                         removeSensorEvent(sensor);
                     } else {
-                        // it should never happen -- just ignore.
+                        // sensor just disconnected -- just ignore.
                     }
                 }
             }
@@ -456,6 +661,11 @@
         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);
@@ -485,6 +695,10 @@
         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;
@@ -523,6 +737,10 @@
         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);
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 2e5cda0..a582492 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -23,6 +23,7 @@
 
 #include <utils/Log.h>
 #include <utils/Looper.h>
+#include <utils/Vector.h>
 
 #include <gui/Sensor.h>
 #include <gui/SensorManager.h>
@@ -45,6 +46,7 @@
 
 struct SensorOffsets
 {
+    jclass      clazz;
     jfieldID    name;
     jfieldID    vendor;
     jfieldID    version;
@@ -60,8 +62,13 @@
     jfieldID    maxDelay;
     jfieldID    flags;
     jmethodID   setType;
+    jmethodID   init;
 } gSensorOffsets;
 
+struct ListOffsets {
+    jclass      clazz;
+    jmethodID   add;
+} gListOffsets;
 
 /*
  * The method below are not thread-safe and not intended to be
@@ -70,8 +77,10 @@
 static void
 nativeClassInit (JNIEnv *_env, jclass _this)
 {
-    jclass sensorClass = _env->FindClass("android/hardware/Sensor");
+    //android.hardware.Sensor
     SensorOffsets& sensorOffsets = gSensorOffsets;
+    jclass sensorClass = (jclass) _env->NewGlobalRef(_env->FindClass("android/hardware/Sensor"));
+    sensorOffsets.clazz       = sensorClass;
     sensorOffsets.name        = _env->GetFieldID(sensorClass, "mName",      "Ljava/lang/String;");
     sensorOffsets.vendor      = _env->GetFieldID(sensorClass, "mVendor",    "Ljava/lang/String;");
     sensorOffsets.version     = _env->GetFieldID(sensorClass, "mVersion",   "I");
@@ -88,7 +97,15 @@
                                                         "Ljava/lang/String;");
     sensorOffsets.maxDelay    = _env->GetFieldID(sensorClass, "mMaxDelay",  "I");
     sensorOffsets.flags = _env->GetFieldID(sensorClass, "mFlags",  "I");
+
     sensorOffsets.setType = _env->GetMethodID(sensorClass, "setType", "(I)Z");
+    sensorOffsets.init = _env->GetMethodID(sensorClass, "<init>", "()V");
+
+    // java.util.List;
+    ListOffsets& listOffsets = gListOffsets;
+    jclass listClass = (jclass) _env->NewGlobalRef(_env->FindClass("java/util/List"));
+    listOffsets.clazz = listClass;
+    listOffsets.add = _env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z");
 }
 
 /**
@@ -141,6 +158,46 @@
     return (jlong) &SensorManager::getInstanceForPackage(String16(opPackageNameUtf.c_str()));
 }
 
+static jobject
+translateNativeSensorToJavaSensor(JNIEnv *env, jobject sensor, const Sensor& nativeSensor) {
+    const SensorOffsets& sensorOffsets(gSensorOffsets);
+
+    if (sensor == NULL) {
+        // Sensor sensor = new Sensor();
+        sensor = env->NewObject(sensorOffsets.clazz, sensorOffsets.init, "");
+    }
+
+    if (sensor != NULL) {
+        jstring name = env->NewStringUTF(nativeSensor.getName().string());
+        jstring vendor = env->NewStringUTF(nativeSensor.getVendor().string());
+        jstring requiredPermission =
+                env->NewStringUTF(nativeSensor.getRequiredPermission().string());
+
+        env->SetObjectField(sensor, sensorOffsets.name,      name);
+        env->SetObjectField(sensor, sensorOffsets.vendor,    vendor);
+        env->SetIntField(sensor, sensorOffsets.version,      nativeSensor.getVersion());
+        env->SetIntField(sensor, sensorOffsets.handle,       nativeSensor.getHandle());
+        env->SetFloatField(sensor, sensorOffsets.range,      nativeSensor.getMaxValue());
+        env->SetFloatField(sensor, sensorOffsets.resolution, nativeSensor.getResolution());
+        env->SetFloatField(sensor, sensorOffsets.power,      nativeSensor.getPowerUsage());
+        env->SetIntField(sensor, sensorOffsets.minDelay,     nativeSensor.getMinDelay());
+        env->SetIntField(sensor, sensorOffsets.fifoReservedEventCount,
+                         nativeSensor.getFifoReservedEventCount());
+        env->SetIntField(sensor, sensorOffsets.fifoMaxEventCount,
+                         nativeSensor.getFifoMaxEventCount());
+        env->SetObjectField(sensor, sensorOffsets.requiredPermission,
+                            requiredPermission);
+        env->SetIntField(sensor, sensorOffsets.maxDelay, nativeSensor.getMaxDelay());
+        env->SetIntField(sensor, sensorOffsets.flags, nativeSensor.getFlags());
+        if (env->CallBooleanMethod(sensor, sensorOffsets.setType, nativeSensor.getType())
+                == JNI_FALSE) {
+            jstring stringType = getInternedString(env, &nativeSensor.getStringType());
+            env->SetObjectField(sensor, sensorOffsets.stringType, stringType);
+        }
+    }
+    return sensor;
+}
+
 static jboolean
 nativeGetSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensor, jint index)
 {
@@ -180,6 +237,24 @@
     return true;
 }
 
+static void
+nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
+
+    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+    const ListOffsets& listOffsets(gListOffsets);
+
+    Vector<Sensor> nativeList;
+
+    mgr->getDynamicSensorList(nativeList);
+
+    ALOGI("DYNS native SensorManager.getDynamicSensorList return %d sensors", nativeList.size());
+    for (size_t i = 0; i < nativeList.size(); ++i) {
+        jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
+        // add to list
+        env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
+    }
+}
+
 static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
     SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
     return mgr->isDataInjectionEnabled();
@@ -235,6 +310,11 @@
                     // step-counter returns a uint64, but the java API only deals with floats
                     float value = float(buffer[i].u64.step_counter);
                     env->SetFloatArrayRegion(mScratch, 0, 1, &value);
+                } else if (buffer[i].type == SENSOR_TYPE_DYNAMIC_SENSOR_META) {
+                    float value[2];
+                    value[0] = buffer[i].dynamic_sensor_meta.connected ? 1.f: 0.f;
+                    value[1] = float(buffer[i].dynamic_sensor_meta.handle);
+                    env->SetFloatArrayRegion(mScratch, 0, 2, value);
                 } else {
                     env->SetFloatArrayRegion(mScratch, 0, 16, buffer[i].data);
                 }
@@ -355,6 +435,10 @@
             "(JLandroid/hardware/Sensor;I)Z",
             (void*)nativeGetSensorAtIndex },
 
+    {"nativeGetDynamicSensors",
+            "(JLjava/util/List;)V",
+            (void*)nativeGetDynamicSensors },
+
     {"nativeIsDataInjectionEnabled",
             "(J)Z",
             (void*)nativeIsDataInjectionEnabled},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3aa7de5..1c3db10 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -420,6 +420,8 @@
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
     <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
 
+    <protected-broadcast android:name="android.intent.action.DYNAMIC_SENSOR_CHANGED" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/services/core/java/com/android/server/SensorNotificationService.java b/services/core/java/com/android/server/SensorNotificationService.java
new file mode 100644
index 0000000..0610464
--- /dev/null
+++ b/services/core/java/com/android/server/SensorNotificationService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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 com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+public class SensorNotificationService extends SystemService implements SensorEventListener {
+    //TODO: set DBG to false or remove Slog before release
+    private static final boolean DBG = true;
+    private static final String TAG = "SensorNotificationService";
+    private Context mContext;
+
+    private SensorManager mSensorManager;
+    private Sensor mMetaSensor;
+
+    public SensorNotificationService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    public void onStart() {
+        LocalServices.addService(SensorNotificationService.class, this);
+    }
+
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+            // start
+            mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+            mMetaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DYNAMIC_SENSOR_META);
+            if (mMetaSensor == null) {
+                if (DBG) Slog.d(TAG, "Cannot obtain dynamic meta sensor, not supported.");
+            } else {
+                mSensorManager.registerListener(this, mMetaSensor,
+                        SensorManager.SENSOR_DELAY_FASTEST);
+            }
+        }
+    }
+
+    private void broadcastDynamicSensorChanged() {
+        Intent i = new Intent(Intent.ACTION_DYNAMIC_SENSOR_CHANGED);
+        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); // avoid waking up manifest receivers
+        mContext.sendBroadcastAsUser(i, UserHandle.ALL);
+        if (DBG) Slog.d(TAG, "DYNS sent dynamic sensor broadcast");
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        if (event.sensor == mMetaSensor) {
+            broadcastDynamicSensorChanged();
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+    }
+}
+
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 361c251..9f320e4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -979,6 +979,7 @@
                     Slog.i(TAG, "Gesture Launcher Service");
                     mSystemServiceManager.startService(GestureLauncherService.class);
                 }
+                mSystemServiceManager.startService(SensorNotificationService.class);
             }
 
             traceBeginAndSlog("StartDiskStatsService");