Access to Device Vibrator State

Add API to access vibrator state and vibrator state listener.

Bug: 135949568
Change-Id: I96636b432d581cea03a9fc9fecba4c08045f5006
diff --git a/api/system-current.txt b/api/system-current.txt
index 51cab13..aacd453 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18,6 +18,7 @@
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
     field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
+    field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
     field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
@@ -9092,6 +9093,17 @@
   @IntDef(flag=true, prefix={"RESTRICTION_"}, value={android.os.UserManager.RESTRICTION_NOT_SET, android.os.UserManager.RESTRICTION_SOURCE_SYSTEM, android.os.UserManager.RESTRICTION_SOURCE_DEVICE_OWNER, android.os.UserManager.RESTRICTION_SOURCE_PROFILE_OWNER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface UserManager.UserRestrictionSource {
   }
 
+  public abstract class Vibrator {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+  }
+
+  public static interface Vibrator.OnVibratorStateChangedListener {
+    method public void onVibratorStateChanged(boolean);
+  }
+
   public class WorkSource implements android.os.Parcelable {
     ctor public WorkSource(int);
     ctor public WorkSource(int, @NonNull String);
diff --git a/api/test-current.txt b/api/test-current.txt
index e352cb6..6a61398 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2605,6 +2605,17 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Waveform> CREATOR;
   }
 
+  public abstract class Vibrator {
+    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public boolean isVibrating();
+    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+  }
+
+  public static interface Vibrator.OnVibratorStateChangedListener {
+    method public void onVibratorStateChanged(boolean);
+  }
+
   public class VintfObject {
     method public static String[] getHalNamesAndVersions();
     method public static String getSepolicyVersion();
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 83f01a5..a7b6516 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware.input;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
@@ -51,6 +52,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 import java.util.List;
 
 /**
@@ -1236,6 +1238,32 @@
         }
 
         @Override
+        public boolean isVibrating() {
+            throw new UnsupportedOperationException(
+                "isVibrating not supported in InputDeviceVibrator");
+        }
+
+        @Override
+        public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            throw new UnsupportedOperationException(
+                "addVibratorStateListener not supported in InputDeviceVibrator");
+        }
+
+        @Override
+        public void addVibratorStateListener(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            throw new UnsupportedOperationException(
+                "addVibratorStateListener not supported in InputDeviceVibrator");
+        }
+
+        @Override
+        public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            throw new UnsupportedOperationException(
+                "removeVibratorStateListener not supported in InputDeviceVibrator");
+        }
+
+        @Override
         public boolean hasAmplitudeControl() {
             return false;
         }
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index e201e43..84013e7 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -18,11 +18,15 @@
 
 import android.os.VibrationEffect;
 import android.os.VibrationAttributes;
+import android.os.IVibratorStateListener;
 
 /** {@hide} */
 interface IVibratorService
 {
     boolean hasVibrator();
+    boolean isVibrating();
+    boolean registerVibratorStateListener(in IVibratorStateListener listener);
+    boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
     boolean hasAmplitudeControl();
     boolean[] areEffectsSupported(in int[] effectIds);
     boolean[] arePrimitivesSupported(in int[] primitiveIds);
diff --git a/core/java/android/os/IVibratorStateListener.aidl b/core/java/android/os/IVibratorStateListener.aidl
new file mode 100644
index 0000000..5ff18a3
--- /dev/null
+++ b/core/java/android/os/IVibratorStateListener.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 2020, 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.os;
+
+/**
+ * Listener for vibrator state.
+ * {@hide}
+ */
+oneway interface IVibratorStateListener {
+    /**
+     * Called when a vibrator start/stop vibrating.
+     * @param state the vibrator state.
+     */
+    void onVibrating(in boolean vibrating);
+}
\ No newline at end of file
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 1d0f9d3..6d8ab6d 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -39,6 +39,11 @@
     }
 
     @Override
+    public boolean isVibrating() {
+        return false;
+    }
+
+    @Override
     public boolean hasAmplitudeControl() {
         return false;
     }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index faf4a36..da20c7f 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,11 +16,17 @@
 
 package android.os;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.AudioAttributes;
+import android.os.IVibratorStateListener;
+import android.util.ArrayMap;
 import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+import java.util.concurrent.Executor;
+import java.util.Objects;
 
 /**
  * Vibrator implementation that controls the main system vibrator.
@@ -32,15 +38,22 @@
 
     private final IVibratorService mService;
     private final Binder mToken = new Binder();
+    private final Context mContext;
+
+    @GuardedBy("mDelegates")
+    private final ArrayMap<OnVibratorStateChangedListener,
+            OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
 
     @UnsupportedAppUsage
     public SystemVibrator() {
+        mContext = null;
         mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
     }
 
     @UnsupportedAppUsage
     public SystemVibrator(Context context) {
         super(context);
+        mContext = context;
         mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
     }
 
@@ -57,6 +70,126 @@
         return false;
     }
 
+    /**
+     * Check whether the vibrator is vibrating.
+     *
+     * @return True if the hardware is vibrating, otherwise false.
+     */
+    @Override
+    public boolean isVibrating() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+            return false;
+        }
+        try {
+            return mService.isVibrating();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    private class OnVibratorStateChangedListenerDelegate extends
+            IVibratorStateListener.Stub {
+        private final Executor mExecutor;
+        private final OnVibratorStateChangedListener mListener;
+
+        OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
+                @NonNull Executor executor) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onVibrating(boolean isVibrating) {
+            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+        }
+    }
+
+    /**
+     * Adds a listener for vibrator state change. If the listener was previously added and not
+     * removed, this call will be ignored.
+     *
+     * @param listener Listener to be added.
+     * @param executor The {@link Executor} on which the listener's callbacks will be executed on.
+     */
+    @Override
+    public void addVibratorStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnVibratorStateChangedListener listener) {
+        Objects.requireNonNull(listener);
+        Objects.requireNonNull(executor);
+        if (mService == null) {
+            Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
+            return;
+        }
+
+        synchronized (mDelegates) {
+            // If listener is already registered, reject and return.
+            if (mDelegates.containsKey(listener)) {
+                Log.w(TAG, "Listener already registered.");
+                return;
+            }
+            try {
+                final OnVibratorStateChangedListenerDelegate delegate =
+                        new OnVibratorStateChangedListenerDelegate(listener, executor);
+                if (!mService.registerVibratorStateListener(delegate)) {
+                    Log.w(TAG, "Failed to register vibrate state listener");
+                    return;
+                }
+                mDelegates.put(listener, delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
+     * If the listener was previously added and not removed, this call will be ignored.
+     *
+     * @param listener listener to be added
+     */
+    @Override
+    public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+        Objects.requireNonNull(listener);
+        if (mContext == null) {
+            Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+            return;
+        }
+        addVibratorStateListener(mContext.getMainExecutor(), listener);
+    }
+
+    /**
+     * Removes the listener for vibrator state changes. If the listener was not previously
+     * registered, this call will do nothing.
+     *
+     * @param listener Listener to be removed.
+     */
+    @Override
+    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+        Objects.requireNonNull(listener);
+        if (mService == null) {
+            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
+            return;
+        }
+        synchronized (mDelegates) {
+            // Check if the listener is registered, otherwise will return.
+            if (mDelegates.containsKey(listener)) {
+                final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
+                try {
+                    if (!mService.unregisterVibratorStateListener(delegate)) {
+                        Log.w(TAG, "Failed to unregister vibrate state listener");
+                        return;
+                    }
+                    mDelegates.remove(listener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
     @Override
     public boolean hasAmplitudeControl() {
         if (mService == null) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f055c60..d4da7a8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,11 +16,14 @@
 
 package android.os;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -29,6 +32,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
 
 /**
  * Class that operates the vibrator on the device.
@@ -395,4 +399,78 @@
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
     public abstract void cancel();
+
+    /**
+     * Check whether the vibrator is vibrating.
+     *
+     * @return True if the hardware is vibrating, otherwise false.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+    public boolean isVibrating() {
+        return false;
+    }
+
+    /**
+    * Listener for when the vibrator state has changed.
+    *
+    * @see #addVibratorStateListener
+    * @see #removeVibratorStateListener
+    * @hide
+    */
+    @SystemApi
+    @TestApi
+    public interface OnVibratorStateChangedListener  {
+        /**
+         * Called when the vibrator state has changed.
+         *
+         * @param isVibrating If true, the vibrator has started vibrating. If false,
+         *                    it's stopped vibrating.
+         */
+        void onVibratorStateChanged(boolean isVibrating);
+    }
+
+    /**
+     * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
+     * If the listener was previously added and not removed, this call will be ignored.
+     *
+     * @param listener listener to be added
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+    public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+    }
+
+    /**
+     * Adds a listener for vibrator state change. If the listener was previously added and not
+     * removed, this call will be ignored.
+     *
+     * @param listener listener to be added
+     * @param executor executor of listener
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+    public void addVibratorStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnVibratorStateChangedListener listener) {
+    }
+
+    /**
+     * Removes the listener for vibrator state changes. If the listener was not previously
+     * registered, this call will do nothing.
+     *
+     * @param listener listener to be removed
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+    }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 71a42e4..e961b74 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1912,6 +1912,15 @@
     <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to the vibrator state.
+         <p>Protection level: signature
+         @hide
+    -->
+    <permission android:name="android.permission.ACCESS_VIBRATOR_STATE"
+        android:label="@string/permdesc_vibrator_state"
+        android:description="@string/permdesc_vibrator_state"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
          from dimming.
          <p>Protection level: normal
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 08c8403..93e73b0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1156,6 +1156,8 @@
     <string name="permlab_vibrate">control vibration</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_vibrate">Allows the app to control the vibrator.</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_vibrator_state">Allows the app to access the vibrator state.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_callPhone">directly call phone numbers</string>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f83fb3f..403e0d1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -378,6 +378,8 @@
         <!-- Permission required for ShortcutManagerUsageTest CTS test. -->
         <permission name="android.permission.ACCESS_SHORTCUTS"/>
         <permission name="android.permission.REBOOT"/>
+        <!-- Permission required for access VIBRATOR_STATE. -->
+        <permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 5946f21..772f6e4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -139,6 +139,7 @@
     <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
     <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index f7d7d6c..cdde67d 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -42,11 +42,13 @@
 import android.os.IBinder;
 import android.os.IExternalVibratorService;
 import android.os.IVibratorService;
+import android.os.IVibratorStateListener;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -166,7 +168,11 @@
     private ExternalVibration mCurrentExternalVibration;
     private boolean mVibratorUnderExternalControl;
     private boolean mLowPowerMode;
+    @GuardedBy("mLock")
     private boolean mIsVibrating;
+    @GuardedBy("mLock")
+    private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
+                new RemoteCallbackList<>();
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
@@ -522,6 +528,75 @@
     }
 
     @Override // Binder call
+    public boolean isVibrating() {
+        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
+            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
+        }
+        synchronized (mLock) {
+            return mIsVibrating;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void notifyStateListenerLocked(IVibratorStateListener listener) {
+        try {
+            listener.onVibrating(mIsVibrating);
+        } catch (RemoteException | RuntimeException e) {
+            Slog.e(TAG, "Vibrator callback failed to call", e);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void notifyStateListenersLocked() {
+        final int length = mVibratorStateListeners.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                final IVibratorStateListener listener =
+                        mVibratorStateListeners.getBroadcastItem(i);
+                notifyStateListenerLocked(listener);
+            }
+        } finally {
+            mVibratorStateListeners.finishBroadcast();
+        }
+    }
+
+    @Override // Binder call
+    public boolean registerVibratorStateListener(IVibratorStateListener listener) {
+        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
+            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
+        }
+        synchronized (mLock) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!mVibratorStateListeners.register(listener)) {
+                    return false;
+                }
+                // Notify its callback after new client registered.
+                notifyStateListenerLocked(listener);
+                return true;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    @Override // Binder call
+    @GuardedBy("mLock")
+    public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
+        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
+            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
+        }
+        synchronized (mLock) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mVibratorStateListeners.unregister(listener);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    @Override // Binder call
     public boolean hasAmplitudeControl() {
         synchronized (mInputDeviceVibrators) {
             // Input device vibrators don't support amplitude controls yet, but are still used over
@@ -1373,7 +1448,10 @@
             FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
                     FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, millis);
             mCurVibUid = uid;
-            mIsVibrating = true;
+            if (!mIsVibrating) {
+                mIsVibrating = true;
+                notifyStateListenersLocked();
+            }
         } catch (RemoteException e) {
         }
     }
@@ -1387,7 +1465,10 @@
             } catch (RemoteException e) { }
             mCurVibUid = -1;
         }
-        mIsVibrating = false;
+        if (mIsVibrating) {
+            mIsVibrating = false;
+            notifyStateListenersLocked();
+        }
     }
 
     private void setVibratorUnderExternalControl(boolean externalControl) {
@@ -1414,6 +1495,8 @@
             pw.print("  mCurrentExternalVibration=" + mCurrentExternalVibration);
             pw.println("  mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
             pw.println("  mIsVibrating=" + mIsVibrating);
+            pw.println("  mVibratorStateListeners Count=" +
+                            mVibratorStateListeners.getRegisteredCallbackCount());
             pw.println("  mLowPowerMode=" + mLowPowerMode);
             pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);