Merge "Add a log and statistics for notification expansion." into lmp-dev
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index f3430e7..243ce97 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -49,6 +49,7 @@
     void onNotificationClear(String pkg, String tag, int id, int userId);
     void onNotificationVisibilityChanged(
             in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys);
+    void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
     void setSystemUiVisibility(int vis, int mask);
     void setHardKeyboardEnabled(boolean enabled);
     void setWindowState(int window, int state);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index dff3f55..87b1f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -96,7 +96,7 @@
 
 public abstract class BaseStatusBar extends SystemUI implements
         CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
-        RecentsComponent.Callbacks {
+        RecentsComponent.Callbacks, ExpandableNotificationRow.ExpansionLogger {
     public static final String TAG = "StatusBar";
     public static final boolean DEBUG = false;
     public static final boolean MULTIUSER_DEBUG = false;
@@ -1035,6 +1035,7 @@
                     Context.LAYOUT_INFLATER_SERVICE);
             row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
                     parent, false);
+            row.setExpansionLogger(this, entry.notification.getKey());
         }
 
         // the notification inspector (see SwipeHelper.setLongPressListener)
@@ -1784,4 +1785,13 @@
         }
         return contextForUser.getPackageManager();
     }
+
+    @Override
+    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        try {
+            mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index e042a53..4b0af11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -54,6 +54,12 @@
     private int mMaxExpandHeight;
     private View mVetoButton;
     private boolean mClearable;
+    private ExpansionLogger mLogger;
+    private String mLoggingKey;
+
+    public interface ExpansionLogger {
+        public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+    }
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -66,6 +72,7 @@
     public void reset() {
         super.reset();
         mRowMinHeight = 0;
+        final boolean wasExpanded = isExpanded();
         mRowMaxHeight = 0;
         mExpandable = false;
         mHasUserChangedExpansion = false;
@@ -76,6 +83,7 @@
         mPublicLayout.reset();
         mPrivateLayout.reset();
         mMaxExpandHeight = 0;
+        logExpansionEvent(false, wasExpanded);
     }
 
     @Override
@@ -131,8 +139,10 @@
      */
     public void setUserExpanded(boolean userExpanded) {
         if (userExpanded && !mExpandable) return;
+        final boolean wasExpanded = isExpanded();
         mHasUserChangedExpansion = true;
         mUserExpanded = userExpanded;
+        logExpansionEvent(true, wasExpanded);
     }
 
     public boolean isUserLocked() {
@@ -156,15 +166,19 @@
      * @param expand whether the system wants this notification to be expanded.
      */
     public void setSystemExpanded(boolean expand) {
+        final boolean wasExpanded = isExpanded();
         mIsSystemExpanded = expand;
         notifyHeightChanged();
+        logExpansionEvent(false, wasExpanded);
     }
 
     /**
      * @param expansionDisabled whether to prevent notification expansion
      */
     public void setExpansionDisabled(boolean expansionDisabled) {
+        final boolean wasExpanded = isExpanded();
         mExpansionDisabled = expansionDisabled;
+        logExpansionEvent(false, wasExpanded);
         notifyHeightChanged();
     }
 
@@ -302,4 +316,17 @@
     private NotificationContentView getShowingLayout() {
         return mShowingPublic ? mPublicLayout : mPrivateLayout;
     }
+
+    public void setExpansionLogger(ExpansionLogger logger, String key) {
+        mLogger = logger;
+        mLoggingKey = key;
+    }
+
+
+    private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
+        final boolean nowExpanded = isExpanded();
+        if (wasExpanded != nowExpanded && mLogger != null) {
+            mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 6fab37c2..3c50947 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -65,6 +65,8 @@
 27501 notification_panel_hidden
 # when notifications are newly displayed on screen, or disappear from screen
 27510 notification_visibility_changed (newlyVisibleKeys|3),(noLongerVisibleKeys|3)
+# when notifications are expanded, or contracted
+27511 notification_expansion (key|3),(user_action|1),(expanded|1)
 # when a notification has been clicked
 27520 notification_clicked (key|3)
 
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index b41b478..1b59f52 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -32,4 +32,5 @@
     boolean allowDisable(int what, IBinder token, String pkg);
     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 bedd369..f9230a3 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -611,6 +611,18 @@
                 }
             }
         }
+
+        @Override
+        public void onNotificationExpansionChanged(String key,
+                boolean userAction, boolean expanded) {
+            EventLogTags.writeNotificationExpansion(key, userAction ? 1 : 0, expanded ? 1 : 0);
+            synchronized (mNotificationList) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.stats.onExpansionChanged(userAction, expanded);
+                }
+            }
+        }
     };
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index adf9516..9b56464 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -200,6 +200,9 @@
         public final Aggregate airtimeCount = new Aggregate();
         public final Aggregate airtimeMs = new Aggregate();
         public final Aggregate posttimeToFirstAirtimeMs = new Aggregate();
+        public final Aggregate userExpansionCount = new Aggregate();
+        public final Aggregate airtimeExpandedMs = new Aggregate();
+        public final Aggregate posttimeToFirstVisibleExpansionMs = new Aggregate();
 
         public AggregatedStats(String key) {
             this.key = key;
@@ -222,6 +225,14 @@
                 posttimeToFirstAirtimeMs.addSample(
                         singleNotificationStats.posttimeToFirstAirtimeMs);
             }
+            if (singleNotificationStats.posttimeToFirstVisibleExpansionMs >= 0) {
+                posttimeToFirstVisibleExpansionMs.addSample(
+                        singleNotificationStats.posttimeToFirstVisibleExpansionMs);
+            }
+            userExpansionCount.addSample(singleNotificationStats.userExpansionCount);
+            if (singleNotificationStats.airtimeExpandedMs >= 0) {
+                airtimeExpandedMs.addSample(singleNotificationStats.airtimeExpandedMs);
+            }
         }
 
         public void dump(PrintWriter pw, String indent) {
@@ -247,6 +258,9 @@
                     indent + "  airtimeCount=" + airtimeCount + ",\n" +
                     indent + "  airtimeMs=" + airtimeMs + ",\n" +
                     indent + "  posttimeToFirstAirtimeMs=" + posttimeToFirstAirtimeMs + ",\n" +
+                    indent + "  userExpansionCount=" + userExpansionCount + ",\n" +
+                    indent + "  airtimeExpandedMs=" + airtimeExpandedMs + ",\n" +
+                    indent + "  posttimeToFVEMs=" + posttimeToFirstVisibleExpansionMs + ",\n" +
                     indent + "}";
         }
     }
@@ -255,6 +269,8 @@
      * Tracks usage of an individual notification that is currently active.
      */
     public static class SingleNotificationStats {
+        private boolean isVisible = false;
+        private boolean isExpanded = false;
         /** SystemClock.elapsedRealtime() when the notification was posted. */
         public long posttimeElapsedMs = -1;
         /** Elapsed time since the notification was posted until it was first clicked, or -1. */
@@ -272,6 +288,20 @@
         public long currentAirtimeStartElapsedMs = -1;
         /** Accumulated visible time. */
         public long airtimeMs = 0;
+        /**
+         * Time in ms between the notification being posted and when it first
+         * became visible and expanded; -1 if it was never visibly expanded.
+         */
+        public long posttimeToFirstVisibleExpansionMs = -1;
+        /**
+         * If currently visible, SystemClock.elapsedRealtime() when the notification was made
+         * visible; -1 otherwise.
+         */
+        public long currentAirtimeExpandedStartElapsedMs = -1;
+        /** Accumulated visible expanded time. */
+        public long airtimeExpandedMs = 0;
+        /** Number of times the notification has been expanded by the user. */
+        public long userExpansionCount = 0;
 
         public long getCurrentPosttimeMs() {
             if (posttimeElapsedMs < 0) {
@@ -284,7 +314,16 @@
             long result = airtimeMs;
             // Add incomplete airtime if currently shown.
             if (currentAirtimeStartElapsedMs >= 0) {
-                result+= (SystemClock.elapsedRealtime() - currentAirtimeStartElapsedMs);
+                result += (SystemClock.elapsedRealtime() - currentAirtimeStartElapsedMs);
+            }
+            return result;
+        }
+
+        public long getCurrentAirtimeExpandedMs() {
+            long result = airtimeExpandedMs;
+            // Add incomplete expanded airtime if currently shown.
+            if (currentAirtimeExpandedStartElapsedMs >= 0) {
+                result += (SystemClock.elapsedRealtime() - currentAirtimeExpandedStartElapsedMs);
             }
             return result;
         }
@@ -318,6 +357,8 @@
 
         public void onVisibilityChanged(boolean visible) {
             long elapsedNowMs = SystemClock.elapsedRealtime();
+            final boolean wasVisible = isVisible;
+            isVisible = visible;
             if (visible) {
                 if (currentAirtimeStartElapsedMs < 0) {
                     airtimeCount++;
@@ -332,6 +373,37 @@
                     currentAirtimeStartElapsedMs = -1;
                 }
             }
+
+            if (wasVisible != isVisible) {
+                updateVisiblyExpandedStats();
+            }
+        }
+
+        public void onExpansionChanged(boolean userAction, boolean expanded) {
+            isExpanded = expanded;
+            if (isExpanded && userAction) {
+                userExpansionCount++;
+            }
+            updateVisiblyExpandedStats();
+        }
+
+        private void updateVisiblyExpandedStats() {
+            long elapsedNowMs = SystemClock.elapsedRealtime();
+            if (isExpanded && isVisible) {
+                // expanded and visible
+                if (currentAirtimeExpandedStartElapsedMs < 0) {
+                    currentAirtimeExpandedStartElapsedMs = elapsedNowMs;
+                }
+                if (posttimeToFirstVisibleExpansionMs < 0) {
+                    posttimeToFirstVisibleExpansionMs = elapsedNowMs - posttimeElapsedMs;
+                }
+            } else {
+                // not-expanded or not-visible
+                if (currentAirtimeExpandedStartElapsedMs >= 0) {
+                    airtimeExpandedMs += (elapsedNowMs - currentAirtimeExpandedStartElapsedMs);
+                    currentAirtimeExpandedStartElapsedMs = -1;
+                }
+            }
         }
 
         /** The notification is leaving the system. Finalize. */
@@ -348,6 +420,9 @@
                     ", airtimeCount=" + airtimeCount +
                     ", airtimeMs=" + airtimeMs +
                     ", currentAirtimeStartElapsedMs=" + currentAirtimeStartElapsedMs +
+                    ", airtimeExpandedMs=" + airtimeExpandedMs +
+                    ", posttimeToFirstVisibleExpansionMs=" + posttimeToFirstVisibleExpansionMs +
+                    ", currentAirtimeExpandedSEMs=" + currentAirtimeExpandedStartElapsedMs +
                     '}';
         }
     }
@@ -393,7 +468,7 @@
         private static final int MSG_DISMISS = 4;
 
         private static final String DB_NAME = "notification_log.db";
-        private static final int DB_VERSION = 2;
+        private static final int DB_VERSION = 3;
 
         /** Age in ms after which events are pruned from the DB. */
         private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L;  // 1 week
@@ -419,6 +494,10 @@
         private static final String COL_ACTION_COUNT = "action_count";
         private static final String COL_POSTTIME_MS = "posttime_ms";
         private static final String COL_AIRTIME_MS = "airtime_ms";
+        private static final String COL_FIRST_EXPANSIONTIME_MS = "first_expansion_time_ms";
+        private static final String COL_AIRTIME_EXPANDED_MS = "expansion_airtime_ms";
+        private static final String COL_EXPAND_COUNT = "expansion_count";
+
 
         private static final int EVENT_TYPE_POST = 1;
         private static final int EVENT_TYPE_CLICK = 2;
@@ -481,6 +560,9 @@
                             COL_ACTION_COUNT + " INT," +
                             COL_POSTTIME_MS + " INT," +
                             COL_AIRTIME_MS + " INT" +
+                            COL_FIRST_EXPANSIONTIME_MS + " INT" +
+                            COL_AIRTIME_EXPANDED_MS + " INT" +
+                            COL_EXPAND_COUNT + " INT" +
                             ")");
                 }
 
@@ -493,6 +575,16 @@
                                     COL_POSTTIME_MS + " INT");
                             db.execSQL("ALTER TABLE " + TAB_LOG + " ADD COLUMN " +
                                     COL_AIRTIME_MS + " INT");
+                        case 2:
+                            // Add COL_EXPANSIONTIME_MS column
+                            db.execSQL("ALTER TABLE " + TAB_LOG + " ADD COLUMN " +
+                                    COL_FIRST_EXPANSIONTIME_MS + " INT");
+                            // Add COL_AIRTIME_EXPANDED_MS column
+                            db.execSQL("ALTER TABLE " + TAB_LOG + " ADD COLUMN " +
+                                    COL_AIRTIME_EXPANDED_MS + " INT");
+                            // Add COL_EXPAND_COUNT column
+                            db.execSQL("ALTER TABLE " + TAB_LOG + " ADD COLUMN " +
+                                    COL_EXPAND_COUNT + " INT");
                     }
                 }
             };
@@ -553,7 +645,7 @@
             if (eventType == EVENT_TYPE_POST) {
                 putNotificationDetails(r, cv);
             } else {
-                putPosttimeAirtime(r, cv);
+                putPosttimeVisibility(r, cv);
             }
             SQLiteDatabase db = mHelper.getWritableDatabase();
             if (db.insert(TAB_LOG, null, cv) < 0) {
@@ -597,9 +689,12 @@
                     r.getNotification().actions.length : 0);
         }
 
-        private static void putPosttimeAirtime(NotificationRecord r, ContentValues outCv) {
+        private static void putPosttimeVisibility(NotificationRecord r, ContentValues outCv) {
             outCv.put(COL_POSTTIME_MS, r.stats.getCurrentPosttimeMs());
             outCv.put(COL_AIRTIME_MS, r.stats.getCurrentAirtimeMs());
+            outCv.put(COL_EXPAND_COUNT, r.stats.userExpansionCount);
+            outCv.put(COL_AIRTIME_EXPANDED_MS, r.stats.getCurrentAirtimeExpandedMs());
+            outCv.put(COL_FIRST_EXPANSIONTIME_MS, r.stats.posttimeToFirstVisibleExpansionMs);
         }
 
         public void dump(PrintWriter pw, String indent, DumpFilter filter) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d0013aa..f33943d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -573,6 +573,19 @@
     }
 
     @Override
+    public void onNotificationExpansionChanged(String key, boolean userAction,
+            boolean expanded) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationExpansionChanged(
+                    key, userAction, expanded);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void onClearAllNotifications(int userId) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();