Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a85c2a00 authored by yoshiki iguchi's avatar yoshiki iguchi
Browse files

Add tests for NotificationGutsManager

This CL adds two tests for NotificationGutsManager:
- opening and closing guts
- Reinflate and replace guts by changing density (or font scale)

Bug: None
Test: Compiled and ran "runtest systemui"

Change-Id: Ic51fa97d18ec78a2c74dd16d01440a2e0f9991ef
parent d6808dc0
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -596,8 +596,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
            entry.row.onDensityOrFontScaleChanged();
            if (exposedGuts) {
                mGutsManager.setExposedGuts(entry.row.getGuts());
                mGutsManager.bindGuts(entry.row);
                mGutsManager.onDensityOrFontScaleChanged(entry.row);
            }
        }
    }
+29 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewAnimationUtils;
@@ -187,6 +188,12 @@ public class NotificationGuts extends FrameLayout {
        }
    }

    public void openControls(
            int x, int y, boolean needsFalsingProtection, @Nullable Runnable onAnimationEnd) {
        animateOpen(x, y, onAnimationEnd);
        setExposed(true /* exposed */, needsFalsingProtection);
    }

    public void closeControls(boolean leavebehinds, boolean controls, int x, int y, boolean force) {
        if (mGutsContent != null) {
            if (mGutsContent.isLeavebehind() && leavebehinds) {
@@ -214,6 +221,27 @@ public class NotificationGuts extends FrameLayout {
        }
    }

    private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
        final double horz = Math.max(getWidth() - x, x);
        final double vert = Math.max(getHeight() - y, y);
        final float r = (float) Math.hypot(horz, vert);

        final Animator a
                = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
        a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
        a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
        a.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (onAnimationEnd != null) {
                    onAnimationEnd.run();
                }
            }
        });
        a.start();
    }

    private void animateClose(int x, int y) {
        if (x == -1 || y == -1) {
            x = (getLeft() + getRight()) / 2;
@@ -279,7 +307,7 @@ public class NotificationGuts extends FrameLayout {
        }
    }

    public void setExposed(boolean exposed, boolean needsFalsingProtection) {
    private void setExposed(boolean exposed, boolean needsFalsingProtection) {
        final boolean wasExposed = mExposed;
        mExposed = exposed;
        mNeedsFalsingProtection = needsFalsingProtection;
+12 −23
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package com.android.systemui.statusbar;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.content.Context;
@@ -32,17 +30,14 @@ import android.util.ArraySet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.accessibility.AccessibilityManager;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.stack.StackStateAnimator;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -112,6 +107,11 @@ public class NotificationGutsManager implements Dumpable {
        mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
    }

    public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) {
        setExposedGuts(row.getGuts());
        bindGuts(row);
    }

    private void saveAndCloseNotificationMenu(
            ExpandableNotificationRow row, NotificationGuts guts, View done) {
        guts.resetFalsingCheck();
@@ -327,26 +327,15 @@ public class NotificationGutsManager implements Dumpable {
                        true /* removeControls */, -1 /* x */, -1 /* y */,
                        false /* resetMenu */);
                guts.setVisibility(View.VISIBLE);
                final double horz = Math.max(guts.getWidth() - x, x);
                final double vert = Math.max(guts.getHeight() - y, y);
                final float r = (float) Math.hypot(horz, vert);
                final Animator a
                        = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
                a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
                a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
                a.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        // Move the notification view back over the menu
                        row.resetTranslation();
                    }
                });
                a.start();

                final boolean needsFalsingProtection =
                        (mPresenter.isPresenterLocked() &&
                                !mAccessibilityManager.isTouchExplorationEnabled());
                guts.setExposed(true /* exposed */, needsFalsingProtection);
                guts.openControls(x, y, needsFalsingProtection, () -> {
                    // Move the notification view back over the menu
                    row.resetTranslation();
                });

                row.closeRemoteInput();
                mListContainer.onHeightChanged(row, true /* needsAnimation */);
                mNotificationGutsExposed = guts;
+196 −0
Original line number Diff line number Diff line
/*
 * 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;

import static junit.framework.Assert.assertNotNull;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;

import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoRule;
import org.mockito.junit.MockitoJUnit;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationGutsManagerTest extends SysuiTestCase {
    private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";

    private final String mPackageName = mContext.getPackageName();
    private final int mUid = Binder.getCallingUid();

    private NotificationChannel mTestNotificationChannel = new NotificationChannel(
            TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
    private TestableLooper mTestableLooper;
    private Handler mHandler;
    private NotificationTestHelper mHelper;
    private NotificationGutsManager mGutsManager;

    @Rule public MockitoRule mockito = MockitoJUnit.rule();
    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
    @Mock private NotificationPresenter mPresenter;
    @Mock private NotificationEntryManager mEntryManager;
    @Mock private NotificationStackScrollLayout mStackScroller;
    @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
    @Mock private NotificationGutsManager.OnSettingsClickListener mOnSettingsClickListener;

    @Before
    public void setUp() {
        mTestableLooper = TestableLooper.get(this);
        mHandler = new Handler(mTestableLooper.getLooper());

        mHelper = new NotificationTestHelper(mContext);

        mGutsManager = new NotificationGutsManager(mContext);
        mGutsManager.setUpWithPresenter(mPresenter, mEntryManager, mStackScroller,
                mCheckSaveListener, mOnSettingsClickListener);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Test methods:

    @Test
    public void testOpenAndCloseGuts() {
        NotificationGuts guts = spy(new NotificationGuts(mContext));
        when(guts.post(any())).thenAnswer(invocation -> {
            mHandler.post(((Runnable) invocation.getArguments()[0]));
            return null;
        });

        // Test doesn't support animation since the guts view is not attached.
        doNothing().when(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));

        ExpandableNotificationRow realRow = createTestNotificationRow();
        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);

        ExpandableNotificationRow row = spy(realRow);
        when(row.getWindowToken()).thenReturn(new Binder());
        when(row.getGuts()).thenReturn(guts);

        mGutsManager.openGuts(row, 0, 0, menuItem);
        assertEquals(View.INVISIBLE, guts.getVisibility());
        mTestableLooper.processAllMessages();
        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));

        assertEquals(View.VISIBLE, guts.getVisibility());
        mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false);

        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
        verify(row, times(1)).setGutsView(any());
    }

    @Test
    public void testChangeDensityOrFontScale() {
        NotificationGuts guts = spy(new NotificationGuts(mContext));
        when(guts.post(any())).thenAnswer(invocation -> {
            mHandler.post(((Runnable) invocation.getArguments()[0]));
            return null;
        });

        // Test doesn't support animation since the guts view is not attached.
        doNothing().when(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));

        ExpandableNotificationRow realRow = createTestNotificationRow();
        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);

        ExpandableNotificationRow row = spy(realRow);
        when(row.getWindowToken()).thenReturn(new Binder());
        when(row.getGuts()).thenReturn(guts);
        doNothing().when(row).inflateGuts();

        mGutsManager.openGuts(row, 0, 0, menuItem);
        mTestableLooper.processAllMessages();
        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));

        row.onDensityOrFontScaleChanged();
        mGutsManager.onDensityOrFontScaleChanged(row);
        mTestableLooper.processAllMessages();

        mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false);

        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
        verify(row, times(2)).setGutsView(any());
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Utility methods:

    private ExpandableNotificationRow createTestNotificationRow() {
        Notification.Builder nb = new Notification.Builder(mContext,
                mTestNotificationChannel.getId())
                                        .setContentTitle("foo")
                                        .setColorized(true)
                                        .setFlag(Notification.FLAG_CAN_COLORIZE, true)
                                        .setSmallIcon(android.R.drawable.sym_def_app_icon);

        try {
            ExpandableNotificationRow row = mHelper.createRow(nb.build());
            row.getEntry().channel = mTestNotificationChannel;
            return row;
        } catch (Exception e) {
            fail();
            return null;
        }
    }

    private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) {
        NotificationMenuRowPlugin menuRow = new NotificationMenuRow(mContext);
        menuRow.createMenu(row, row.getStatusBarNotification());

        NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext);
        assertNotNull(menuItem);
        return menuItem;
    }
}