diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 5e610ed32d006..40c009f3a4771 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -40,8 +40,10 @@ interface IStatusBarService // You need the STATUS_BAR_SERVICE permission void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList, out int[] switches, out List binders); - void onPanelRevealed(); + void onPanelRevealed(boolean clearNotificationEffects); void onPanelHidden(); + // Mark current notifications as "seen" and stop ringing, vibrating, blinking. + void clearNotificationEffects(); void onNotificationClick(String key); void onNotificationActionClick(String key, int actionIndex); void onNotificationError(String pkg, String tag, int id, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 6ea5c70c95f88..89a2c7447f847 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -28,6 +28,7 @@ public interface DozeHost { void pulseWhileDozing(@NonNull PulseCallback callback, int reason); void stopDozing(); boolean isPowerSaveActive(); + boolean isNotificationLightOn(); public interface Callback { void onNewNotifications(); @@ -40,4 +41,4 @@ public interface DozeHost { void onPulseStarted(); void onPulseFinished(); } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 1e29476178760..2341144da159f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -56,6 +56,17 @@ public class DozeService extends DreamService { private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse"; private static final String EXTRA_INSTANCE = "instance"; + /** + * Earliest time we pulse due to a notification light after the service started. + * + *

Incoming notification light events during the blackout period are + * delayed to the earliest time defined by this constant.

+ * + *

This delay avoids a pulse immediately after screen off, at which + * point the notification light is re-enabled again by NoMan.

+ */ + private static final int EARLIEST_LIGHT_PULSE_AFTER_START_MS = 10 * 1000; + private final String mTag = String.format(TAG + ".%08x", hashCode()); private final Context mContext = this; private final DozeParameters mDozeParameters = new DozeParameters(mContext); @@ -77,6 +88,7 @@ public class DozeService extends DreamService { private boolean mPowerSaveActive; private boolean mCarMode; private long mNotificationPulseTime; + private long mEarliestPulseDueToLight; private int mScheduleResetsRemaining; public DozeService() { @@ -161,8 +173,9 @@ public class DozeService extends DreamService { } mDreaming = true; - listenForPulseSignals(true); rescheduleNotificationPulse(false /*predicate*/); // cancel any pending pulse alarms + mEarliestPulseDueToLight = System.currentTimeMillis() + EARLIEST_LIGHT_PULSE_AFTER_START_MS; + listenForPulseSignals(true); // Ask the host to get things ready to start dozing. // Once ready, we call startDozing() at which point the CPU may suspend @@ -298,6 +311,12 @@ public class DozeService extends DreamService { if (listen) { resetNotificationResets(); mHost.addCallback(mHostCallback); + + // Continue to pulse for existing LEDs. + mNotificationLightOn = mHost.isNotificationLightOn(); + if (mNotificationLightOn) { + updateNotificationPulseDueToLight(); + } } else { mHost.removeCallback(mHostCallback); } @@ -308,21 +327,26 @@ public class DozeService extends DreamService { mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets(); } - private void updateNotificationPulse() { - if (DEBUG) Log.d(mTag, "updateNotificationPulse"); + private void updateNotificationPulseDueToLight() { + long timeMs = System.currentTimeMillis(); + timeMs = Math.max(timeMs, mEarliestPulseDueToLight); + updateNotificationPulse(timeMs); + } + + private void updateNotificationPulse(long notificationTimeMs) { + if (DEBUG) Log.d(mTag, "updateNotificationPulse notificationTimeMs=" + notificationTimeMs); if (!mDozeParameters.getPulseOnNotifications()) return; if (mScheduleResetsRemaining <= 0) { if (DEBUG) Log.d(mTag, "No more schedule resets remaining"); return; } - final long now = System.currentTimeMillis(); - if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) { + if ((notificationTimeMs - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) { if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule"); return; } mScheduleResetsRemaining--; if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining); - mNotificationPulseTime = now; + mNotificationPulseTime = notificationTimeMs; rescheduleNotificationPulse(true /*predicate*/); } @@ -404,14 +428,14 @@ public class DozeService extends DreamService { private final DozeHost.Callback mHostCallback = new DozeHost.Callback() { @Override public void onNewNotifications() { - if (DEBUG) Log.d(mTag, "onNewNotifications"); + if (DEBUG) Log.d(mTag, "onNewNotifications (noop)"); // noop for now } @Override public void onBuzzBeepBlinked() { if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked"); - updateNotificationPulse(); + updateNotificationPulse(System.currentTimeMillis()); } @Override @@ -420,7 +444,7 @@ public class DozeService extends DreamService { if (mNotificationLightOn == on) return; mNotificationLightOn = on; if (mNotificationLightOn) { - updateNotificationPulse(); + updateNotificationPulseDueToLight(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 581b30f8aae27..57ac4b0dad506 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -1648,7 +1648,11 @@ public abstract class BaseStatusBar extends SystemUI implements protected void handleVisibleToUserChanged(boolean visibleToUser) { try { if (visibleToUser) { - mBarService.onPanelRevealed(); + // Only stop blinking, vibrating, ringing when the user went into the shade + // manually (SHADE or SHADE_LOCKED). + boolean clearNotificationEffects = + (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); + mBarService.onPanelRevealed(clearNotificationEffects); } else { mBarService.onPanelHidden(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 02807a10570b4..7a3b5e4784152 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -3795,6 +3795,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, * @param state The {@link StatusBarState} to set. */ public void setBarState(int state) { + // If we're visible and switched to SHADE_LOCKED (the user dragged down + // on the lockscreen), clear notification LED, vibration, ringing. + // Other transitions are covered in handleVisibleToUserChanged(). + if (mVisible && mState != state && state == StatusBarState.SHADE_LOCKED) { + try { + mBarService.clearNotificationEffects(); + } catch (RemoteException e) { + // Ignore. + } + } mState = state; mStatusBarWindowManager.setStatusBarState(state); } @@ -4126,6 +4136,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private final ArrayList mCallbacks = new ArrayList(); private final H mHandler = new H(); + // Keeps the last reported state by fireNotificationLight. + private boolean mNotificationLightOn; + @Override public String toString() { return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]"; @@ -4144,6 +4157,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void fireNotificationLight(boolean on) { + mNotificationLightOn = on; for (Callback callback : mCallbacks) { callback.onNotificationLight(on); } @@ -4185,6 +4199,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mBatteryController != null && mBatteryController.isPowerSave(); } + @Override + public boolean isNotificationLightOn() { + return mNotificationLightOn; + } + private void handleStartDozing(@NonNull Runnable ready) { if (!mDozing) { mDozing = true; diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 24fc45508bc1b..fdb443e1753d9 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -26,8 +26,9 @@ public interface NotificationDelegate { void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id, int uid, int initialPid, String message, int userId); - void onPanelRevealed(); + void onPanelRevealed(boolean clearEffects); void onPanelHidden(); + void clearEffects(); void onNotificationVisibilityChanged( String[] newlyVisibleKeys, String[] noLongerVisibleKeys); void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c851868ac85d8..323b34b49ef68 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -123,7 +123,7 @@ import java.util.Objects; /** {@hide} */ public class NotificationManagerService extends SystemService { static final String TAG = "NotificationService"; - static final boolean DBG = false; + static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); static final int MAX_PACKAGE_NOTIFICATIONS = 50; @@ -577,9 +577,23 @@ public class NotificationManagerService extends SystemService { } @Override - public void onPanelRevealed() { + public void onPanelRevealed(boolean clearEffects) { EventLogTags.writeNotificationPanelRevealed(); + if (clearEffects) { + clearEffects(); + } + } + + @Override + public void onPanelHidden() { + EventLogTags.writeNotificationPanelHidden(); + } + + @Override + public void clearEffects() { synchronized (mNotificationList) { + if (DBG) Slog.d(TAG, "clearEffects"); + // sound mSoundNotification = null; @@ -610,11 +624,6 @@ public class NotificationManagerService extends SystemService { } } - @Override - public void onPanelHidden() { - EventLogTags.writeNotificationPanelHidden(); - } - @Override public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id, int uid, int initialPid, String message, int userId) { @@ -753,8 +762,10 @@ public class NotificationManagerService extends SystemService { // Keep track of screen on/off state, but do not turn off the notification light // until user passes through the lock screen or views the notification. mScreenOn = true; + updateNotificationPulse(); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { mScreenOn = false; + updateNotificationPulse(); } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { mInCall = TelephonyManager.EXTRA_STATE_OFFHOOK .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE)); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 9828cd4a30d6f..cf2ed07425f66 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.util.Slog; -import android.view.WindowManager; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; @@ -495,16 +494,26 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } /** - * The status bar service should call this each time the user brings the panel from - * invisible to visible in order to clear the notification light. + * @param clearNotificationEffects whether to consider notifications as "shown" and stop + * LED, vibration, and ringing */ @Override - public void onPanelRevealed() { + public void onPanelRevealed(boolean clearNotificationEffects) { enforceStatusBarService(); long identity = Binder.clearCallingIdentity(); try { - // tell the notification manager to turn off the lights. - mNotificationDelegate.onPanelRevealed(); + mNotificationDelegate.onPanelRevealed(clearNotificationEffects); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void clearNotificationEffects() throws RemoteException { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.clearEffects(); } finally { Binder.restoreCallingIdentity(identity); }