OSDN Git Service

Fixed an issue where the qs panel wasn't properly placed
authorSelim Cinek <cinek@google.com>
Fri, 2 Jun 2017 00:36:46 +0000 (17:36 -0700)
committerSelim Cinek <cinek@google.com>
Wed, 7 Jun 2017 19:08:39 +0000 (19:08 +0000)
The QS panel should only be placed below the notifications
if it has notifications above the shelf. This ensures that
all animations including icons are appearing in the right
place.

Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
Fixes: 35305470
Change-Id: I97fb0c76ed69b7638ce83a7c06856d87bf6bf65f

12 files changed:
packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfChangedListener.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfObserver.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java [new file with mode: 0644]

index c445c0d..bf6db23 100644 (file)
@@ -63,6 +63,8 @@ import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -162,6 +164,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
     private FalsingManager mFalsingManager;
+    private AboveShelfChangedListener mAboveShelfChangedListener;
     private HeadsUpManager mHeadsUpManager;
 
     private boolean mJustClicked;
@@ -388,6 +391,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
         expandedIcon.setStaticDrawableColor(color);
     }
 
+    public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) {
+        mAboveShelfChangedListener = aboveShelfChangedListener;
+    }
+
     @Override
     public boolean isDimmable() {
         if (!getShowingLayout().isDimmable()) {
@@ -442,6 +449,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     }
 
     public void setHeadsUp(boolean isHeadsUp) {
+        boolean wasAboveShelf = isAboveShelf();
         int intrinsicBefore = getIntrinsicHeight();
         mIsHeadsUp = isHeadsUp;
         mPrivateLayout.setHeadsUp(isHeadsUp);
@@ -454,6 +462,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
         }
         if (isHeadsUp) {
             setAboveShelf(true);
+        } else if (isAboveShelf() != wasAboveShelf) {
+            mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
         }
     }
 
@@ -634,6 +644,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
      */
     public void setPinned(boolean pinned) {
         int intrinsicHeight = getIntrinsicHeight();
+        boolean wasAboveShelf = isAboveShelf();
         mIsPinned = pinned;
         if (intrinsicHeight != getIntrinsicHeight()) {
             notifyHeightChanged(false /* needsAnimation */);
@@ -645,6 +656,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
             setUserExpanded(true);
         }
         setChronometerRunning(mLastChronometerRunning);
+        if (isAboveShelf() != wasAboveShelf) {
+            mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+        }
     }
 
     public boolean isPinned() {
@@ -993,8 +1007,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        boolean wasAboveShelf = isAboveShelf();
         mHeadsupDisappearRunning = headsUpAnimatingAway;
         mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        if (isAboveShelf() != wasAboveShelf) {
+            mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+        }
     }
 
     /**
@@ -1555,6 +1573,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
      */
     public void setOnKeyguard(boolean onKeyguard) {
         if (onKeyguard != mOnKeyguard) {
+            boolean wasAboveShelf = isAboveShelf();
             final boolean wasExpanded = isExpanded();
             mOnKeyguard = onKeyguard;
             onExpansionChanged(false /* userAction */, wasExpanded);
@@ -1564,6 +1583,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
                 }
                 notifyHeightChanged(false /* needsAnimation */);
             }
+            if (isAboveShelf() != wasAboveShelf) {
+                mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+            }
         }
     }
 
@@ -2214,7 +2236,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     }
 
     public void setAboveShelf(boolean aboveShelf) {
+        boolean wasAboveShelf = isAboveShelf();
         mAboveShelf = aboveShelf;
+        if (isAboveShelf() != wasAboveShelf) {
+            mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+        }
     }
 
     public static class NotificationViewState extends ExpandableViewState {
index 3f1f82c..d2c0f64 100644 (file)
@@ -179,7 +179,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
                 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
             }
             mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
-            mShelfState.hidden = !mAmbientState.isShadeExpanded();
+            mShelfState.hidden = !mAmbientState.isShadeExpanded()
+                    || mAmbientState.isQsCustomizerShowing();
             mShelfState.maxShelfEnd = maxShelfEnd;
         } else {
             mShelfState.hidden = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfChangedListener.java
new file mode 100644 (file)
index 0000000..07baedc
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * A listener for when the above shelf state of notification changes
+ */
+public interface AboveShelfChangedListener {
+
+    /**
+     * Notifies a listener that the above shelf state changed
+     */
+    void onAboveShelfStateChanged(boolean aboveShelf);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfObserver.java
new file mode 100644 (file)
index 0000000..f10d2d7
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+/**
+ * An observer that listens to the above shelf state and can notify listeners
+ */
+public class AboveShelfObserver implements AboveShelfChangedListener {
+
+    private final ViewGroup mHostLayout;
+    private boolean mHasViewsAboveShelf = false;
+    private HasViewAboveShelfChangedListener mListener;
+
+    public AboveShelfObserver(ViewGroup hostLayout) {
+        mHostLayout = hostLayout;
+    }
+
+    public void setListener(HasViewAboveShelfChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onAboveShelfStateChanged(boolean aboveShelf) {
+        boolean hasViewsAboveShelf = aboveShelf;
+        if (!hasViewsAboveShelf && mHostLayout != null) {
+            int n = mHostLayout.getChildCount();
+            for (int i = 0; i < n; i++) {
+                View child = mHostLayout.getChildAt(i);
+                if (child instanceof ExpandableNotificationRow) {
+                    if (((ExpandableNotificationRow) child).isAboveShelf()) {
+                        hasViewsAboveShelf = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (mHasViewsAboveShelf != hasViewsAboveShelf) {
+            mHasViewsAboveShelf = hasViewsAboveShelf;
+            if (mListener != null) {
+                mListener.onHasViewsAboveShelfChanged(hasViewsAboveShelf);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    boolean hasViewsAboveShelf() {
+        return mHasViewsAboveShelf;
+    }
+
+    public interface HasViewAboveShelfChangedListener {
+        void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf);
+    }
+}
index 1952a21..76cc0ff 100644 (file)
@@ -34,18 +34,19 @@ import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 /**
  * The container with notification stack scroller and quick settings inside.
  */
 public class NotificationsQuickSettingsContainer extends FrameLayout
-        implements OnInflateListener, FragmentListener, OnHeadsUpChangedListener {
+        implements OnInflateListener, FragmentListener,
+        AboveShelfObserver.HasViewAboveShelfChangedListener {
 
     private FrameLayout mQsFrame;
     private View mUserSwitcher;
-    private View mStackScroller;
+    private NotificationStackScrollLayout mStackScroller;
     private View mKeyguardStatusBar;
     private boolean mInflated;
     private boolean mQsExpanded;
@@ -53,8 +54,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
 
     private int mBottomPadding;
     private int mStackScrollerMargin;
-    private boolean mHeadsUp;
-    private HeadsUpManager mHeadsUpManager;
+    private boolean mHasViewsAboveShelf;
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -76,16 +76,12 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         FragmentHostManager.get(this).addTagListener(QS.TAG, this);
-        mHeadsUpManager = SysUiServiceProvider.getComponent(getContext(), StatusBar.class)
-                .mHeadsUpManager;
-        mHeadsUpManager.addListener(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         FragmentHostManager.get(this).removeTagListener(QS.TAG, this);
-        mHeadsUpManager.removeListener(this);
     }
 
     @Override
@@ -116,7 +112,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
         boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
         boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
 
-        final boolean qsBottom = mHeadsUp;
+        final boolean qsBottom = mHasViewsAboveShelf;
         View stackQsTop = qsBottom ? mStackScroller : mQsFrame;
         View stackQsBottom = !qsBottom ? mStackScroller : mQsFrame;
         // Invert the order of the scroll view and user switcher such that the notifications receive
@@ -183,7 +179,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
             setPadding(0, 0, 0, mBottomPadding);
             setBottomMargin(mStackScroller, mStackScrollerMargin);
         }
-
+        mStackScroller.setQsCustomizerShowing(isShowing);
     }
 
     private void setBottomMargin(View v, int bottomMargin) {
@@ -193,12 +189,8 @@ public class NotificationsQuickSettingsContainer extends FrameLayout
     }
 
     @Override
-    public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
-        boolean hasHeadsUp = mHeadsUpManager.getAllEntries().size() != 0;
-        if (mHeadsUp == hasHeadsUp) {
-            return;
-        }
-        mHeadsUp = hasHeadsUp;
+    public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
+        mHasViewsAboveShelf = hasViewsAboveShelf;
         invalidate();
     }
 }
index 3906e40..4c819c3 100644 (file)
@@ -209,6 +209,7 @@ import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.RowInflaterTask;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -1000,6 +1001,9 @@ public class StatusBar extends SystemUI implements DemoMode,
                 R.id.notification_stack_scroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
+        mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
+        mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
+                R.id.notification_container_parent));
         mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
 
         mNotificationIconAreaController = SystemUIFactory.getInstance()
@@ -5333,6 +5337,8 @@ public class StatusBar extends SystemUI implements DemoMode,
     // for heads up notifications
     protected HeadsUpManager mHeadsUpManager;
 
+    private AboveShelfObserver mAboveShelfObserver;
+
     // handling reordering
     protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
 
@@ -6408,6 +6414,7 @@ public class StatusBar extends SystemUI implements DemoMode,
         row.setExpansionLogger(this, entry.notification.getKey());
         row.setGroupManager(mGroupManager);
         row.setHeadsUpManager(mHeadsUpManager);
+        row.setAboveShelfChangedListener(mAboveShelfObserver);
         row.setRemoteInputController(mRemoteInputController);
         row.setOnExpandClickListener(this);
         row.setRemoteViewClickHandler(mOnClickHandler);
index 70f2260..53dfb24 100644 (file)
@@ -20,6 +20,7 @@ import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.support.v4.util.ArraySet;
@@ -65,7 +66,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
     private final ArrayMap<String, Long> mSnoozedPackages;
     private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
     private final int mDefaultSnoozeLengthMs;
-    private final Handler mHandler = new Handler();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
 
         private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
@@ -624,6 +625,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
 
     @Override
     public void onReorderingAllowed() {
+        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
         for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (isHeadsUp(entry.key)) {
                 // Maybe the heads-up was removed already
@@ -631,6 +633,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
             }
         }
         mEntriesToRemoveWhenReorderingAllowed.clear();
+        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
     }
 
     public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
index 41ef781..b7b991e 100644 (file)
@@ -61,6 +61,7 @@ public class AmbientState {
     private boolean mPanelFullWidth;
     private boolean mHasPulsingNotifications;
     private boolean mUnlockHintRunning;
+    private boolean mQsCustomizerShowing;
 
     public AmbientState(Context context) {
         reload(context);
@@ -314,4 +315,12 @@ public class AmbientState {
     public boolean isUnlockHintRunning() {
         return mUnlockHintRunning;
     }
+
+    public boolean isQsCustomizerShowing() {
+        return mQsCustomizerShowing;
+    }
+
+    public void setQsCustomizerShowing(boolean qsCustomizerShowing) {
+        mQsCustomizerShowing = qsCustomizerShowing;
+    }
 }
index 84acaf1..a3d812c 100644 (file)
@@ -375,6 +375,7 @@ public class NotificationStackScrollLayout extends ViewGroup
     private boolean mHeadsUpAnimatingAway;
     private int mStatusBarState;
     private int mCachedBackgroundColor;
+    private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mAnimateScroll = this::animateScroll;
 
     public NotificationStackScrollLayout(Context context) {
@@ -4060,7 +4061,7 @@ public class NotificationStackScrollLayout extends ViewGroup
     }
 
     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
-        if (mAnimationsEnabled) {
+        if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
             mNeedsAnimation = true;
             if (!mIsExpanded && !isHeadsUp) {
@@ -4242,6 +4243,15 @@ public class NotificationStackScrollLayout extends ViewGroup
         mAmbientState.setUnlockHintRunning(running);
     }
 
+    public void setQsCustomizerShowing(boolean isShowing) {
+        mAmbientState.setQsCustomizerShowing(isShowing);
+        requestChildrenUpdate();
+    }
+
+    public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
+        mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
index 99b664a..2d46537 100644 (file)
@@ -29,6 +29,7 @@ import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 
+import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
 import com.android.systemui.SysuiTestCase;
 
@@ -96,4 +97,40 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
         row.setHideSensitive(true, false, 0, 0);
         verify(row).updateShelfIconColor();
     }
+
+    @Test
+    public void testAboveShelfChangedListenerCalled() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+        row.setAboveShelfChangedListener(listener);
+        row.setHeadsUp(true);
+        verify(listener).onAboveShelfStateChanged(true);
+    }
+
+    @Test
+    public void testAboveShelfChangedListenerCalledPinned() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+        row.setAboveShelfChangedListener(listener);
+        row.setPinned(true);
+        verify(listener).onAboveShelfStateChanged(true);
+    }
+
+    @Test
+    public void testAboveShelfChangedListenerCalledHeadsUpGoingAway() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+        row.setAboveShelfChangedListener(listener);
+        row.setHeadsUpAnimatingAway(true);
+        verify(listener).onAboveShelfStateChanged(true);
+    }
+    @Test
+    public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        row.setHeadsUp(true);
+        AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+        row.setAboveShelfChangedListener(listener);
+        row.setAboveShelf(false);
+        verify(listener).onAboveShelfStateChanged(false);
+    }
 }
index cb238dd..6e7477f 100644 (file)
@@ -24,12 +24,16 @@ import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.view.LayoutInflater;
+import android.widget.FrameLayout;
 import android.widget.RemoteViews;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationInflaterTest;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
  * A helper class to create {@link ExpandableNotificationRow}
@@ -42,10 +46,12 @@ public class NotificationTestHelper {
     private final NotificationGroupManager mGroupManager = new NotificationGroupManager();
     private ExpandableNotificationRow mRow;
     private InflationException mException;
+    private HeadsUpManager mHeadsUpManager;
 
     public NotificationTestHelper(Context context) {
         mContext = context;
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager);
     }
 
     public ExpandableNotificationRow createRow() throws Exception {
@@ -73,6 +79,8 @@ public class NotificationTestHelper {
         });
         ExpandableNotificationRow row = mRow;
         row.setGroupManager(mGroupManager);
+        row.setHeadsUpManager(mHeadsUpManager);
+        row.setAboveShelfChangedListener(aboveShelf -> {});
         UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
         StatusBarNotification sbn = new StatusBarNotification("com.android.systemui",
                 "com.android.systemui", mId++, null, 1000,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
new file mode 100644 (file)
index 0000000..1ee9b32
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.FrameLayout;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationTestHelper;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AboveShelfObserverTest extends SysuiTestCase {
+
+    private AboveShelfObserver mObserver;
+    private FrameLayout mHostLayout;
+    private NotificationTestHelper mNotificationTestHelper;
+    private AboveShelfObserver.HasViewAboveShelfChangedListener mListener;
+
+    @Before
+    public void setUp() throws Exception {
+        mNotificationTestHelper = new NotificationTestHelper(getContext());
+        mHostLayout = new FrameLayout(getContext());
+        mObserver = new AboveShelfObserver(mHostLayout);
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        row.setAboveShelfChangedListener(mObserver);
+        mHostLayout.addView(row);
+        row = mNotificationTestHelper.createRow();
+        row.setAboveShelfChangedListener(mObserver);
+        mHostLayout.addView(row);
+        mListener = mock(AboveShelfObserver.HasViewAboveShelfChangedListener.class);
+    }
+
+    @Test
+    public void testObserverChangesWhenGoingAbove() {
+        ExpandableNotificationRow row = (ExpandableNotificationRow) mHostLayout.getChildAt(0);
+        mObserver.setListener(mListener);
+        row.setHeadsUp(true);
+        verify(mListener).onHasViewsAboveShelfChanged(true);
+    }
+
+    @Test
+    public void testObserverChangesWhenGoingBelow() {
+        ExpandableNotificationRow row = (ExpandableNotificationRow) mHostLayout.getChildAt(0);
+        row.setHeadsUp(true);
+        mObserver.setListener(mListener);
+        row.setHeadsUp(false);
+        verify(mListener).onHasViewsAboveShelfChanged(false);
+    }
+
+    @Test
+    public void testStaysAboveWhenOneGoesAway() {
+        ExpandableNotificationRow row = (ExpandableNotificationRow) mHostLayout.getChildAt(0);
+        row.setHeadsUp(true);
+        row = (ExpandableNotificationRow) mHostLayout.getChildAt(1);
+        row.setHeadsUp(true);
+        row.setHeadsUp(false);
+        Assert.assertTrue("There are still views above the shelf but removing one cleared it",
+                mObserver.hasViewsAboveShelf());
+    }
+}
+