Merge "fix getIfaceStats and getTotalStats bug"
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index d160b73..1e915f8 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -17,18 +17,18 @@
 
 package com.android.commands.media;
 
-import android.app.ActivityManager;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.media.MediaMetadata;
-import android.media.session.ControllerCallbackLink;
-import android.media.session.ISessionController;
 import android.media.session.ISessionManager;
+import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.HandlerThread;
-import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -48,6 +48,8 @@
 public class Media extends BaseCommand {
     // This doesn't belongs to any package. Setting the package name to empty string.
     private static final String PACKAGE_NAME = "";
+    private static ActivityThread sThread;
+    private static MediaSessionManager sMediaSessionManager;
     private ISessionManager mSessionService;
 
     /**
@@ -80,6 +82,13 @@
 
     @Override
     public void onRun() throws Exception {
+        if (sThread == null) {
+            Looper.prepareMainLooper();
+            sThread = ActivityThread.systemMain();
+            Context context = sThread.getSystemContext();
+            sMediaSessionManager =
+                    (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        }
         mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
                 Context.MEDIA_SESSION_SERVICE));
         if (mSessionService == null) {
@@ -117,12 +126,11 @@
             showError("Error: must include a session id");
             return;
         }
+
         boolean success = false;
         try {
-            List<IBinder> sessions = mSessionService
-                    .getSessions(null, ActivityManager.getCurrentUser());
-            for (IBinder session : sessions) {
-                ISessionController controller = ISessionController.Stub.asInterface(session);
+            List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
+            for (MediaController controller : controllers) {
                 try {
                     if (controller != null && id.equals(controller.getTag())) {
                         ControllerMonitor monitor = new ControllerMonitor(controller);
@@ -178,14 +186,14 @@
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
     }
 
-    class ControllerCallbackStub extends ControllerCallbackLink.CallbackStub {
+    class ControllerCallback extends MediaController.Callback {
         @Override
         public void onSessionDestroyed() {
             System.out.println("onSessionDestroyed. Enter q to quit.");
         }
 
         @Override
-        public void onEvent(String event, Bundle extras) {
+        public void onSessionEvent(String event, Bundle extras) {
             System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
         }
 
@@ -218,25 +226,25 @@
         }
 
         @Override
-        public void onVolumeInfoChanged(PlaybackInfo info) {
-            System.out.println("onVolumeInfoChanged " + info);
+        public void onAudioInfoChanged(PlaybackInfo info) {
+            System.out.println("onAudioInfoChanged " + info);
         }
     }
 
     private class ControllerMonitor {
-        private final ISessionController mController;
-        private final ControllerCallbackLink mControllerCallbackLink;
+        private final MediaController mController;
+        private final ControllerCallback mControllerCallback;
 
-        ControllerMonitor(ISessionController controller) {
+        ControllerMonitor(MediaController controller) {
             mController = controller;
-            mControllerCallbackLink = new ControllerCallbackLink(new ControllerCallbackStub());
+            mControllerCallback = new ControllerCallback();
         }
 
         void printUsageMessage() {
             try {
                 System.out.println("V2Monitoring session " + mController.getTag()
                         + "...  available commands: play, pause, next, previous");
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 System.out.println("Error trying to monitor session!");
             }
             System.out.println("(q)uit: finish monitoring");
@@ -248,8 +256,8 @@
                 @Override
                 protected void onLooperPrepared() {
                     try {
-                        mController.registerCallbackListener(PACKAGE_NAME, mControllerCallbackLink);
-                    } catch (RemoteException e) {
+                        mController.registerCallback(mControllerCallback);
+                    } catch (RuntimeException e) {
                         System.out.println("Error registering monitor callback");
                     }
                 }
@@ -291,7 +299,7 @@
             } finally {
                 cbThread.getLooper().quit();
                 try {
-                    mController.unregisterCallbackListener(mControllerCallbackLink);
+                    mController.unregisterCallback(mControllerCallback);
                 } catch (Exception e) {
                     // ignoring
                 }
@@ -305,9 +313,9 @@
             KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
             try {
-                mController.sendMediaButton(PACKAGE_NAME, null, false, down);
-                mController.sendMediaButton(PACKAGE_NAME, null, false, up);
-            } catch (RemoteException e) {
+                mController.dispatchMediaButtonEvent(down);
+                mController.dispatchMediaButtonEvent(up);
+            } catch (RuntimeException e) {
                 System.out.println("Failed to dispatch " + keyCode);
             }
         }
@@ -316,16 +324,13 @@
     private void runListSessions() {
         System.out.println("Sessions:");
         try {
-            List<IBinder> sessions = mSessionService
-                    .getSessions(null, ActivityManager.getCurrentUser());
-            for (IBinder session : sessions) {
-
-                ISessionController controller = ISessionController.Stub.asInterface(session);
+            List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
+            for (MediaController controller : controllers) {
                 if (controller != null) {
                     try {
                         System.out.println("  tag=" + controller.getTag()
                                 + ", package=" + controller.getPackageName());
-                    } catch (RemoteException e) {
+                    } catch (RuntimeException e) {
                         // ignore
                     }
                 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 63fd563..ad34ab3 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -173,7 +173,8 @@
     }
 
     /**
-     * Implement this to know when a notification is expanded / collapsed.
+     * Implement this to know when a notification change (expanded / collapsed) is visible to user.
+     *
      * @param key the notification key
      * @param isUserAction whether the expanded change is caused by user action.
      * @param isExpanded whether the notification is expanded.
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 499d493..f346b00 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -344,7 +344,6 @@
                 if (entry != null) {
                     entry.setSeen();
                     mAgingHelper.onNotificationSeen(entry);
-                    mSmartActionsHelper.onNotificationSeen(entry);
                 }
             }
         } catch (Throwable e) {
diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
index acf1180..ce2c409 100644
--- a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
+++ b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
@@ -236,10 +236,6 @@
         return mSeen;
     }
 
-    public boolean isExpanded() {
-        return mExpanded;
-    }
-
     public boolean isShowActionEventLogged() {
         return mIsShowActionEventLogged;
     }
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 95df5f2..0d528e7 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -152,18 +152,26 @@
         return replies;
     }
 
-    void onNotificationSeen(@NonNull NotificationEntry entry) {
-        if (entry.isExpanded()) {
-            maybeSendActionShownEvent(entry);
-        }
-    }
-
     void onNotificationExpansionChanged(@NonNull NotificationEntry entry, boolean isUserAction,
             boolean isExpanded) {
-        // Notification can be expanded in the background, and thus the isUserAction check.
-        if (isUserAction && isExpanded) {
-            maybeSendActionShownEvent(entry);
+        if (!isExpanded) {
+            return;
         }
+        String resultId = mNotificationKeyToResultIdCache.get(entry.getSbn().getKey());
+        if (resultId == null) {
+            return;
+        }
+        // Only report if this is the first time the user sees these suggestions.
+        if (entry.isShowActionEventLogged()) {
+            return;
+        }
+        entry.setShowActionEventLogged();
+        TextClassifierEvent textClassifierEvent =
+                createTextClassifierEventBuilder(TextClassifierEvent.TYPE_ACTIONS_SHOWN,
+                        resultId)
+                        .build();
+        // TODO: If possible, report which replies / actions are actually seen by user.
+        mTextClassifier.onTextClassifierEvent(textClassifierEvent);
     }
 
     void onNotificationDirectReplied(@NonNull String key) {
@@ -234,26 +242,6 @@
                 .setResultId(resultId);
     }
 
-    private void maybeSendActionShownEvent(@NonNull NotificationEntry entry) {
-        if (mTextClassifier == null) {
-            return;
-        }
-        String resultId = mNotificationKeyToResultIdCache.get(entry.getSbn().getKey());
-        if (resultId == null) {
-            return;
-        }
-        // Only report if this is the first time the user sees these suggestions.
-        if (entry.isShowActionEventLogged()) {
-            return;
-        }
-        entry.setShowActionEventLogged();
-        TextClassifierEvent textClassifierEvent =
-                createTextClassifierEventBuilder(TextClassifierEvent.TYPE_ACTIONS_SHOWN, resultId)
-                        .build();
-        // TODO: If possible, report which replies / actions are actually seen by user.
-        mTextClassifier.onTextClassifierEvent(textClassifierEvent);
-    }
-
     /**
      * Returns whether a notification is eligible for action adjustments.
      *
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index 7b7ce3d..707349b 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -273,24 +273,22 @@
         final String message = "Where are you?";
         Notification notification = mNotificationBuilder.setContentText(message).build();
         when(mNotificationEntry.getNotification()).thenReturn(notification);
-        when(mNotificationEntry.isExpanded()).thenReturn(false);
 
         mSmartActionsHelper.suggestReplies(mNotificationEntry);
-        mSmartActionsHelper.onNotificationSeen(mNotificationEntry);
+        mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, false, false);
 
         verify(mTextClassifier, never()).onTextClassifierEvent(
                 Mockito.any(TextClassifierEvent.class));
     }
 
     @Test
-    public void testOnNotificationsSeen_expanded() {
+    public void testOnNotifications_expanded() {
         final String message = "Where are you?";
         Notification notification = mNotificationBuilder.setContentText(message).build();
         when(mNotificationEntry.getNotification()).thenReturn(notification);
-        when(mNotificationEntry.isExpanded()).thenReturn(true);
 
         mSmartActionsHelper.suggestReplies(mNotificationEntry);
-        mSmartActionsHelper.onNotificationSeen(mNotificationEntry);
+        mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, false, true);
 
         ArgumentCaptor<TextClassifierEvent> argumentCaptor =
                 ArgumentCaptor.forClass(TextClassifierEvent.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
index cc302b1..0b8596f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
@@ -27,13 +27,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.Build;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.ViewGroup;
 
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -43,6 +40,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationInflater;
@@ -71,7 +69,6 @@
             Dependency.get(NotificationInterruptionStateProvider.class);
 
     private final Context mContext;
-    private final IStatusBarService mBarService;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
@@ -85,6 +82,7 @@
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
+    private final NotificationLogger mNotificationLogger = Dependency.get(NotificationLogger.class);
 
     @Inject
     public NotificationRowBinder(Context context,
@@ -92,8 +90,6 @@
         mContext = context;
         mMessagingUtil = new NotificationMessagingUtil(context);
         mAllowLongPress = allowLongPress;
-        mBarService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
     private NotificationRemoteInputManager getRemoteInputManager() {
@@ -274,13 +270,7 @@
     }
 
     private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
-            } catch (RemoteException e) {
-                // Ignore.
-            }
-        });
+         mNotificationLogger.onExpansionChanged(key, userAction, expanded);
     }
 
     /** Callback for when a row is bound to an entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 3eec38e..5ba9b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -23,9 +23,12 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -42,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -66,6 +70,7 @@
     private final UiOffloadThread mUiOffloadThread;
     private final NotificationEntryManager mEntryManager;
     private HeadsUpManager mHeadsUpManager;
+    private final ExpansionStateLogger mExpansionStateLogger;
 
     protected Handler mHandler = new Handler();
     protected IStatusBarService mBarService;
@@ -144,6 +149,9 @@
             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
             mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
 
+            mExpansionStateLogger.onVisibilityChanged(
+                    mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications);
+
             recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
             mTmpCurrentlyVisibleNotifications.clear();
             mTmpNewlyVisibleNotifications.clear();
@@ -155,12 +163,14 @@
     public NotificationLogger(NotificationListener notificationListener,
             UiOffloadThread uiOffloadThread,
             NotificationEntryManager entryManager,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            ExpansionStateLogger expansionStateLogger) {
         mNotificationListener = notificationListener;
         mUiOffloadThread = uiOffloadThread;
         mEntryManager = entryManager;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mExpansionStateLogger = expansionStateLogger;
         // Not expected to be destroyed, don't need to unsubscribe
         statusBarStateController.addCallback(this);
 
@@ -173,6 +183,7 @@
                 if (removedByUser && visibility != null) {
                     logNotificationClear(entry.key, entry.notification, visibility);
                 }
+                mExpansionStateLogger.onEntryRemoved(entry.key);
             }
 
             @Override
@@ -319,8 +330,8 @@
         }
     }
 
-    private NotificationVisibility[] cloneVisibilitiesAsArr(Collection<NotificationVisibility> c) {
-
+    private static NotificationVisibility[] cloneVisibilitiesAsArr(
+            Collection<NotificationVisibility> c) {
         final NotificationVisibility[] array = new NotificationVisibility[c.size()];
         int i = 0;
         for(NotificationVisibility nv: c) {
@@ -348,9 +359,133 @@
     }
 
     /**
+     * Called when the notification is expanded / collapsed.
+     */
+    public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded);
+    }
+
+    /**
      * A listener that is notified when some child locations might have changed.
      */
     public interface OnChildLocationsChangedListener {
         void onChildLocationsChanged();
     }
+
+    /**
+     * Logs the expansion state change when the notification is visible.
+     */
+    public static class ExpansionStateLogger {
+        /** Notification key -> state, should be accessed in UI offload thread only. */
+        private final Map<String, State> mExpansionStates = new ArrayMap<>();
+
+        /**
+         * Notification key -> last logged expansion state, should be accessed in UI thread only.
+         */
+        private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>();
+        private final UiOffloadThread mUiOffloadThread;
+        @VisibleForTesting
+        IStatusBarService mBarService;
+
+        @Inject
+        public ExpansionStateLogger(UiOffloadThread uiOffloadThread) {
+            mUiOffloadThread = uiOffloadThread;
+            mBarService =
+                    IStatusBarService.Stub.asInterface(
+                            ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        }
+
+        @VisibleForTesting
+        void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+            State state = getState(key);
+            state.mIsUserAction = isUserAction;
+            state.mIsExpanded = isExpanded;
+            maybeNotifyOnNotificationExpansionChanged(key, state);
+        }
+
+        @VisibleForTesting
+        void onVisibilityChanged(
+                Collection<NotificationVisibility> newlyVisible,
+                Collection<NotificationVisibility> noLongerVisible) {
+            final NotificationVisibility[] newlyVisibleAr =
+                    cloneVisibilitiesAsArr(newlyVisible);
+            final NotificationVisibility[] noLongerVisibleAr =
+                    cloneVisibilitiesAsArr(noLongerVisible);
+
+            for (NotificationVisibility nv : newlyVisibleAr) {
+                State state = getState(nv.key);
+                state.mIsVisible = true;
+                maybeNotifyOnNotificationExpansionChanged(nv.key, state);
+            }
+            for (NotificationVisibility nv : noLongerVisibleAr) {
+                State state = getState(nv.key);
+                state.mIsVisible = false;
+            }
+        }
+
+        @VisibleForTesting
+        void onEntryRemoved(String key) {
+            mExpansionStates.remove(key);
+            mLoggedExpansionState.remove(key);
+        }
+
+        private State getState(String key) {
+            State state = mExpansionStates.get(key);
+            if (state == null) {
+                state = new State();
+                mExpansionStates.put(key, state);
+            }
+            return state;
+        }
+
+        private void maybeNotifyOnNotificationExpansionChanged(final String key, State state) {
+            if (!state.isFullySet()) {
+                return;
+            }
+            if (!state.mIsVisible) {
+                return;
+            }
+            Boolean loggedExpansionState = mLoggedExpansionState.get(key);
+            // Consider notification is initially collapsed, so only expanded is logged in the
+            // first time.
+            if (loggedExpansionState == null && !state.mIsExpanded) {
+                return;
+            }
+            if (loggedExpansionState != null
+                    && state.mIsExpanded == loggedExpansionState) {
+                return;
+            }
+            mLoggedExpansionState.put(key, state.mIsExpanded);
+            final State stateToBeLogged = new State(state);
+            mUiOffloadThread.submit(() -> {
+                try {
+                    mBarService.onNotificationExpansionChanged(
+                            key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
+                }
+            });
+        }
+
+        private static class State {
+            @Nullable
+            Boolean mIsUserAction;
+            @Nullable
+            Boolean mIsExpanded;
+            @Nullable
+            Boolean mIsVisible;
+
+            private State() {}
+
+            private State(State state) {
+                this.mIsUserAction = state.mIsUserAction;
+                this.mIsExpanded = state.mIsExpanded;
+                this.mIsVisible = state.mIsVisible;
+            }
+
+            private boolean isFullySet() {
+                return mIsUserAction != null && mIsExpanded != null && mIsVisible != null;
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
new file mode 100644
index 0000000..4b03399
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -0,0 +1,145 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.logging;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ExpansionStateLoggerTest extends SysuiTestCase {
+    private static final String NOTIFICATION_KEY = "notin_key";
+
+    private NotificationLogger.ExpansionStateLogger mLogger;
+    @Mock
+    private IStatusBarService mBarService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLogger = new NotificationLogger.ExpansionStateLogger(
+                Dependency.get(UiOffloadThread.class));
+        mLogger.mBarService = mBarService;
+    }
+
+    @Test
+    public void testVisible() throws RemoteException {
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        waitForUiOffloadThread();
+
+        verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
+                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testExpanded() throws RemoteException {
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        waitForUiOffloadThread();
+
+        verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
+                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testVisibleAndNotExpanded() throws RemoteException {
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false);
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        waitForUiOffloadThread();
+
+        verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
+                eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testVisibleAndExpanded() throws RemoteException {
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true);
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        waitForUiOffloadThread();
+
+        verify(mBarService).onNotificationExpansionChanged(
+                NOTIFICATION_KEY, true, true);
+    }
+
+    @Test
+    public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException {
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        waitForUiOffloadThread();
+
+        verify(mBarService).onNotificationExpansionChanged(
+                NOTIFICATION_KEY, false, true);
+    }
+
+    @Test
+    public void testExpandedAndVisible_visibleBeforeExpanded() throws RemoteException {
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        waitForUiOffloadThread();
+
+        verify(mBarService).onNotificationExpansionChanged(
+                NOTIFICATION_KEY, false, true);
+    }
+
+    @Test
+    public void testExpandedAndVisible_logOnceOnly() throws RemoteException {
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        waitForUiOffloadThread();
+
+        verify(mBarService).onNotificationExpansionChanged(
+                NOTIFICATION_KEY, false, true);
+    }
+
+    private NotificationVisibility createNotificationVisibility(String key, boolean visibility) {
+        return NotificationVisibility.obtain(key, 0, 0, visibility);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 6472589..db2706b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -72,6 +72,7 @@
     @Mock private IStatusBarService mBarService;
     @Mock private NotificationData mNotificationData;
     @Mock private ExpandableNotificationRow mRow;
+    @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
 
     // Dependency mocks:
     @Mock private NotificationEntryManager mEntryManager;
@@ -98,7 +99,8 @@
         mEntry.setRow(mRow);
 
         mLogger = new TestableNotificationLogger(mListener, Dependency.get(UiOffloadThread.class),
-                mEntryManager, mock(StatusBarStateController.class), mBarService);
+                mEntryManager, mock(StatusBarStateController.class), mBarService,
+                mExpansionStateLogger);
         mLogger.setUpWithContainer(mListContainer);
         verify(mEntryManager).addNotificationEntryListener(mEntryListenerCaptor.capture());
         mNotificationEntryListener = mEntryListenerCaptor.getValue();
@@ -166,8 +168,10 @@
                 UiOffloadThread uiOffloadThread,
                 NotificationEntryManager entryManager,
                 StatusBarStateController statusBarStateController,
-                IStatusBarService barService) {
-            super(notificationListener, uiOffloadThread, entryManager, statusBarStateController);
+                IStatusBarService barService,
+                ExpansionStateLogger expansionStateLogger) {
+            super(notificationListener, uiOffloadThread, entryManager, statusBarStateController,
+                    expansionStateLogger);
             mBarService = barService;
             // Make this on the current thread so we can wait for it during tests.
             mHandler = Handler.createAsync(Looper.myLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 0d4046b..17611ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -159,6 +159,8 @@
     private NotificationFilter mNotificationFilter;
     @Mock
     private NotificationAlertingManager mNotificationAlertingManager;
+    @Mock
+    private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
 
     private TestableStatusBar mStatusBar;
     private FakeMetricsLogger mMetricsLogger;
@@ -207,7 +209,8 @@
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mEntryManager = new TestableNotificationEntryManager(mContext);
         mNotificationLogger = new NotificationLogger(mNotificationListener,
-                Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController);
+                Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController,
+                mExpansionStateLogger);
         mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
         DozeLog.traceDozing(mContext, false /* dozing */);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1798617..20c4da4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -878,7 +878,6 @@
                     if (r.hasBeenVisiblyExpanded()) {
                         logSmartSuggestionsVisible(r);
                     }
-                    final long now = System.currentTimeMillis();
                     if (userAction) {
                         MetricsLogger.action(r.getItemLogMaker()
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
@@ -888,9 +887,6 @@
                         r.recordExpanded();
                         reportUserInteraction(r);
                     }
-                    EventLogTags.writeNotificationExpansion(key,
-                            userAction ? 1 : 0, expanded ? 1 : 0,
-                            r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
                     mAssistants.notifyAssistantExpansionChangedLocked(r.sbn, userAction, expanded);
                 }
             }