diff options
6 files changed, 148 insertions, 0 deletions
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index dfdb9aead2fd..6428e150a52b 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -47,6 +47,8 @@ interface IStatusBarService int uid, int initialPid, String message, int userId); void onClearAllNotifications(int userId); void onNotificationClear(String pkg, String tag, int id, int userId); + void onNotificationVisibilityChanged( + in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys); void setSystemUiVisibility(int vis, int mask); void setHardKeyboardEnabled(boolean enabled); void toggleRecentApps(); 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 1d01f91f32eb..e89544cd0f90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -63,6 +63,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.StatusBarNotification; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -111,10 +112,14 @@ import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; +import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; public class PhoneStatusBar extends BaseStatusBar implements DemoMode { static final String TAG = "PhoneStatusBar"; @@ -147,6 +152,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; private static final long AUTOHIDE_TIMEOUT_MS = 3000; + /** The minimum delay in ms between reports of notification visibility. */ + private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; + // fling gesture tuning parameters, scaled to display density private float mSelfExpandVelocityPx; // classic value: 2000px/s private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") @@ -376,6 +384,82 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { mOnFlipRunnable = onFlipRunnable; } + /** Keys of notifications currently visible to the user. */ + private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>(); + private long mLastVisibilityReportUptimeMs; + + private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD + | ViewState.LOCATION_TOP_STACK_PEEKING + | ViewState.LOCATION_MAIN_AREA + | ViewState.LOCATION_BOTTOM_STACK_PEEKING; + + private final OnChildLocationsChangedListener mNotificationLocationsChangedListener = + new OnChildLocationsChangedListener() { + @Override + public void onChildLocationsChanged( + NotificationStackScrollLayout stackScrollLayout) { + if (mHandler.hasCallbacks(mVisibilityReporter)) { + // Visibilities will be reported when the existing + // callback is executed. + return; + } + // Calculate when we're allowed to run the visibility + // reporter. Note that this timestamp might already have + // passed. That's OK, the callback will just be executed + // ASAP. + long nextReportUptimeMs = + mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; + mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); + } + }; + + // Tracks notifications currently visible in mNotificationStackScroller and + // emits visibility events via NoMan on changes. + private final Runnable mVisibilityReporter = new Runnable() { + private final ArrayList<String> mTmpNewlyVisibleNotifications = new ArrayList<String>(); + private final ArrayList<String> mTmpCurrentlyVisibleNotifications = new ArrayList<String>(); + + @Override + public void run() { + mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); + + // 1. Loop over mNotificationData entries: + // A. Keep list of visible notifications. + // B. Keep list of previously hidden, now visible notifications. + // 2. Compute no-longer visible notifications by removing currently + // visible notifications from the set of previously visible + // notifications. + // 3. Report newly visible and no-longer visible notifications. + // 4. Keep currently visible notifications for next report. + int N = mNotificationData.size(); + for (int i = 0; i < N; i++) { + Entry entry = mNotificationData.get(i); + String key = entry.notification.getKey(); + boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(key); + boolean currentlyVisible = + (mStackScroller.getChildLocation(entry.row) & VISIBLE_LOCATIONS) != 0; + if (currentlyVisible) { + // Build new set of visible notifications. + mTmpCurrentlyVisibleNotifications.add(key); + } + if (!previouslyVisible && currentlyVisible) { + mTmpNewlyVisibleNotifications.add(key); + } + } + ArraySet<String> noLongerVisibleNotifications = mCurrentlyVisibleNotifications; + noLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); + + logNotificationVisibilityChanges( + mTmpNewlyVisibleNotifications, noLongerVisibleNotifications); + + mCurrentlyVisibleNotifications.clear(); + mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); + + mTmpNewlyVisibleNotifications.clear(); + mTmpCurrentlyVisibleNotifications.clear(); + } + }; + @Override public void setZenMode(int mode) { super.setZenMode(mode); @@ -2647,6 +2731,41 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { if (false) Log.v(TAG, "updateResources"); } + // Visibility reporting + + @Override + protected void visibilityChanged(boolean visible) { + if (visible) { + mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener); + } else { + // Report all notifications as invisible and turn down the + // reporter. + if (!mCurrentlyVisibleNotifications.isEmpty()) { + logNotificationVisibilityChanges( + Collections.<String>emptyList(), mCurrentlyVisibleNotifications); + mCurrentlyVisibleNotifications.clear(); + } + mHandler.removeCallbacks(mVisibilityReporter); + mStackScroller.setChildLocationsChangedListener(null); + } + super.visibilityChanged(visible); + } + + private void logNotificationVisibilityChanges( + Collection<String> newlyVisible, Collection<String> noLongerVisible) { + if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { + return; + } + + String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]); + String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]); + try { + mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); + } catch (RemoteException e) { + // Ignore. + } + } + // // tracing // diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 976893475b60..5083d44d2d82 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -63,6 +63,8 @@ option java_package com.android.server 27500 notification_panel_revealed # when the notification panel is hidden 27501 notification_panel_hidden +# when notifications are newly displayed on screen, or disappear from screen +27510 notification_visibility_changed (newlyVisibleKeys|3),(noLongerVisibleKeys|3) # --------------------------- # Watchdog.java diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index e0591a284f09..ce4c1ed98e37 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -31,4 +31,6 @@ public interface NotificationDelegate { void onPanelRevealed(); void onPanelHidden(); boolean allowDisable(int what, IBinder token, String pkg); + void onNotificationVisibilityChanged( + String[] newlyVisibleKeys, String[] noLongerVisibleKeys); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6534783c5654..5c14de10767d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1071,6 +1071,16 @@ public class NotificationManagerService extends SystemService { } return true; } + + @Override + public void onNotificationVisibilityChanged( + String[] newlyVisibleKeys, String[] noLongerVisibleKeys) { + // Using ';' as separator since eventlogs uses ',' to separate + // args. + EventLogTags.writeNotificationVisibilityChanged( + TextUtils.join(";", newlyVisibleKeys), + TextUtils.join(";", noLongerVisibleKeys)); + } }; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index e4b5f3ae0d41..91f796be2b67 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -588,6 +588,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub } @Override + public void onNotificationVisibilityChanged( + String[] newlyVisibleKeys, String[] noLongerVisibleKeys) throws RemoteException { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onNotificationVisibilityChanged( + newlyVisibleKeys, noLongerVisibleKeys); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void onClearAllNotifications(int userId) { enforceStatusBarService(); final int callingUid = Binder.getCallingUid(); |