// You need the STATUS_BAR_SERVICE permission
void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
out int[] switches, out List<IBinder> 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,
void pulseWhileDozing(@NonNull PulseCallback callback);
void stopDozing();
boolean isPowerSaveActive();
+ boolean isNotificationLightOn();
public interface Callback {
void onNewNotifications();
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.
+ *
+ * <p>Incoming notification light events during the blackout period are
+ * delayed to the earliest time defined by this constant.</p>
+ *
+ * <p>This delay avoids a pulse immediately after screen off, at which
+ * point the notification light is re-enabled again by NoMan.</p>
+ */
+ 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);
private boolean mPowerSaveActive;
private boolean mCarMode;
private long mNotificationPulseTime;
+ private long mEarliestPulseDueToLight;
private int mScheduleResetsRemaining;
public DozeService() {
}
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
if (listen) {
resetNotificationResets();
mHost.addCallback(mHostCallback);
+
+ // Continue to pulse for existing LEDs.
+ mNotificationLightOn = mHost.isNotificationLightOn();
+ if (mNotificationLightOn) {
+ updateNotificationPulseDueToLight();
+ }
} else {
mHost.removeCallback(mHostCallback);
}
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*/);
}
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
if (mNotificationLightOn == on) return;
mNotificationLightOn = on;
if (mNotificationLightOn) {
- updateNotificationPulse();
+ updateNotificationPulseDueToLight();
}
}
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();
}
* @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);
}
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
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() + "]";
}
public void fireNotificationLight(boolean on) {
+ mNotificationLightOn = on;
for (Callback callback : mCallbacks) {
callback.onNotificationLight(on);
}
return mBatteryController != null && mBatteryController.isPowerSave();
}
+ @Override
+ public boolean isNotificationLightOn() {
+ return mNotificationLightOn;
+ }
+
private void handleStartDozing(@NonNull Runnable ready) {
if (!mDozing) {
mDozing = true;
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);
/** {@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;
}
@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;
}
@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) {
Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
// 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));
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;
}
/**
- * 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);
}