Merge "Write eventlog entries for notification visibility"
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index dfdb9ae..6428e15 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -47,6 +47,8 @@
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 1d01f91..e89544c 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.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.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 @@
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 @@
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 @@
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 9768934..5083d44 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -63,6 +63,8 @@
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 e0591a2..ce4c1ed 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 @@
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 6534783..5c14de1 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 @@
}
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 e4b5f3a..91f796b 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 @@
}
@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();