/*
 * 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.row;

import static android.app.NotificationManager.IMPORTANCE_DEFAULT;

import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_ALL;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.AppOpsManager;
import android.app.NotificationChannel;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;

import org.junit.Assert;
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.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.List;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class ExpandableNotificationRowTest extends SysuiTestCase {

    private ExpandableNotificationRow mGroupRow;

    private NotificationTestHelper mNotificationTestHelper;
    boolean mHeadsUpAnimatingAway = false;

    @Rule public MockitoRule mockito = MockitoJUnit.rule();
    @Mock private NotificationBlockingHelperManager mBlockingHelperManager;

    @Before
    public void setUp() throws Exception {
        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
        mNotificationTestHelper = new NotificationTestHelper(mContext);
        mGroupRow = mNotificationTestHelper.createGroup();
        mGroupRow.setHeadsUpAnimatingAwayListener(
                animatingAway -> mHeadsUpAnimatingAway = animatingAway);
        mDependency.injectTestDependency(
                NotificationBlockingHelperManager.class,
                mBlockingHelperManager);
    }

    @Test
    public void testGroupSummaryNotShowingIconWhenPublic() {
        mGroupRow.setSensitive(true, true);
        mGroupRow.setHideSensitiveForIntrinsicHeight(true);
        assertTrue(mGroupRow.isSummaryWithChildren());
        assertFalse(mGroupRow.isShowingIcon());
    }

    @Test
    public void testNotificationHeaderVisibleWhenAnimating() {
        mGroupRow.setSensitive(true, true);
        mGroupRow.setHideSensitive(true, false, 0, 0);
        mGroupRow.setHideSensitive(false, true, 0, 0);
        assertTrue(mGroupRow.getChildrenContainer().getVisibleHeader().getVisibility()
                == View.VISIBLE);
    }

    @Test
    public void testUserLockedResetEvenWhenNoChildren() {
        mGroupRow.setUserLocked(true);
        mGroupRow.removeAllChildren();
        mGroupRow.setUserLocked(false);
        assertFalse("The childrencontainer should not be userlocked but is, the state "
                + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
    }

    @Test
    public void testReinflatedOnDensityChange() {
        mGroupRow.setUserLocked(true);
        mGroupRow.removeAllChildren();
        mGroupRow.setUserLocked(false);
        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
        mGroupRow.setChildrenContainer(mockContainer);
        mGroupRow.onDensityOrFontScaleChanged();
        verify(mockContainer).reInflateViews(any(), any());
    }

    @Test
    public void testIconColorShouldBeUpdatedWhenSensitive() throws Exception {
        ExpandableNotificationRow row = spy(mNotificationTestHelper.createRow(
                FLAG_CONTENT_VIEW_ALL));
        row.setSensitive(true, true);
        row.setHideSensitive(true, false, 0, 0);
        verify(row).updateShelfIconColor();
    }

    @Test
    public void testFreeContentViewWhenSafe() throws Exception {
        ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);

        row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);

        assertNull(row.getPrivateLayout().getHeadsUpChild());
    }

    @Test
    public void setNeedsRedactionSetsInflationFlag() throws Exception {
        ExpandableNotificationRow row = mNotificationTestHelper.createRow();

        row.setNeedsRedaction(true);

        assertTrue(row.getNotificationInflater().isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC));
    }

    @Test
    public void setNeedsRedactionFreesViewWhenFalse() throws Exception {
        ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);
        row.setNeedsRedaction(true);
        row.getPublicLayout().setVisibility(View.GONE);

        row.setNeedsRedaction(false);

        assertNull(row.getPublicLayout().getContractedChild());
    }

    @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);
    }

    @Test
    public void testClickSound() throws Exception {
        assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
        StatusBarStateController mock = mock(StatusBarStateController.class);
        when(mock.isDozing()).thenReturn(true);
        mGroupRow.setStatusBarStateController(mock);
        mGroupRow.setSecureStateProvider(()-> false);
        assertFalse("Shouldn't play sounds when dark and trusted.",
                mGroupRow.isSoundEffectsEnabled());
        mGroupRow.setSecureStateProvider(()-> true);
        assertTrue("Should always play sounds when not trusted.",
                mGroupRow.isSoundEffectsEnabled());
    }

    @Test
    public void testSetDismissed_longPressListenerRemoved() {
        ExpandableNotificationRow.LongPressListener listener =
                mock(ExpandableNotificationRow.LongPressListener.class);
        mGroupRow.setLongPressListener(listener);
        mGroupRow.doLongClickCallback(0,0);
        verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0),
                any(NotificationMenuRowPlugin.MenuItem.class));
        reset(listener);

        mGroupRow.setDismissed(true);
        mGroupRow.doLongClickCallback(0,0);
        verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0),
                any(NotificationMenuRowPlugin.MenuItem.class));
    }

    @Test
    public void testShowAppOps_noHeader() {
        // public notification is custom layout - no header
        mGroupRow.setSensitive(true, true);
        mGroupRow.setAppOpsOnClickListener(null);
        mGroupRow.showAppOpsIcons(null);
    }

    @Test
    public void testShowAppOpsIcons_header() {
        NotificationHeaderView mockHeader = mock(NotificationHeaderView.class);

        NotificationContentView publicLayout = mock(NotificationContentView.class);
        mGroupRow.setPublicLayout(publicLayout);
        NotificationContentView privateLayout = mock(NotificationContentView.class);
        mGroupRow.setPrivateLayout(privateLayout);
        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
        when(mockContainer.getNotificationChildCount()).thenReturn(1);
        when(mockContainer.getHeaderView()).thenReturn(mockHeader);
        mGroupRow.setChildrenContainer(mockContainer);

        ArraySet<Integer> ops = new ArraySet<>();
        ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
        mGroupRow.showAppOpsIcons(ops);

        verify(mockHeader, times(1)).showAppOpsIcons(ops);
        verify(privateLayout, times(1)).showAppOpsIcons(ops);
        verify(publicLayout, times(1)).showAppOpsIcons(ops);

    }

    @Test
    public void testAppOpsOnClick() {
        ExpandableNotificationRow.OnAppOpsClickListener l = mock(
                ExpandableNotificationRow.OnAppOpsClickListener.class);
        View view = mock(View.class);

        mGroupRow.setAppOpsOnClickListener(l);

        mGroupRow.getAppOpsOnClickListener().onClick(view);
        verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
    }

    @Test
    public void testHeadsUpAnimatingAwayListener() {
        mGroupRow.setHeadsUpAnimatingAway(true);
        Assert.assertEquals(true, mHeadsUpAnimatingAway);
        mGroupRow.setHeadsUpAnimatingAway(false);
        Assert.assertEquals(false, mHeadsUpAnimatingAway);
    }

    @Test
    public void testPerformDismissWithBlockingHelper_falseWhenBlockingHelperIsntShown() {
        when(mBlockingHelperManager.perhapsShowBlockingHelper(
                eq(mGroupRow), any(NotificationMenuRowPlugin.class))).thenReturn(false);

        assertFalse(
                mGroupRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
    }

    @Test
    public void testPerformDismissWithBlockingHelper_doesntPerformOnGroupSummary() {
        ExpandableNotificationRow childRow = mGroupRow.getChildrenContainer().getViewAtPosition(0);
        when(mBlockingHelperManager.perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class)))
                .thenReturn(true);

        assertTrue(
                childRow.performDismissWithBlockingHelper(false /* fromAccessibility */));

        verify(mBlockingHelperManager, times(1))
                .perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class));
        verify(mBlockingHelperManager, times(0))
                .perhapsShowBlockingHelper(eq(mGroupRow), any(NotificationMenuRowPlugin.class));
    }

    @Test
    public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
        mGroupRow.setBlockingHelperShowing(true);
        assertTrue(mGroupRow.isBlockingHelperShowing());

        mGroupRow.setBlockingHelperShowing(false);
        assertFalse(mGroupRow.isBlockingHelperShowing());
    }

    @Test
    public void testGetNumUniqueChildren_defaultChannel() {
        assertEquals(1, mGroupRow.getNumUniqueChannels());
    }

    @Test
    public void testGetNumUniqueChildren_multiChannel() {
        List<ExpandableNotificationRow> childRows =
                mGroupRow.getChildrenContainer().getNotificationChildren();
        // Give each child a unique channel id/name.
        int i = 0;
        for (ExpandableNotificationRow childRow : childRows) {
            childRow.getEntry().channel =
                    new NotificationChannel("id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT);
            i++;
        }

        assertEquals(3, mGroupRow.getNumUniqueChannels());
    }

    @Test
    public void testIconScrollXAfterTranslationAndReset() throws Exception {
        mGroupRow.setTranslation(50);
        assertEquals(50, -mGroupRow.getEntry().expandedIcon.getScrollX());

        mGroupRow.resetTranslation();
        assertEquals(0, mGroupRow.getEntry().expandedIcon.getScrollX());
    }

    @Test
    public void testIsExpanded_userExpanded() {
        mGroupRow.setExpandable(true);
        Assert.assertFalse(mGroupRow.isExpanded());
        mGroupRow.setUserExpanded(true);
        Assert.assertTrue(mGroupRow.isExpanded());
    }

    @Test
    public void testGetIsNonblockable() throws Exception {
        ExpandableNotificationRow row =
                mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());

        assertFalse(row.getIsNonblockable());
    }

    @Test
    public void testGetIsNonblockable_oemLocked() throws Exception {
        ExpandableNotificationRow row =
                mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
        row.getEntry().channel.setImportanceLockedByOEM(true);

        assertTrue(row.getIsNonblockable());
    }

    @Test
    public void testGetIsNonblockable_criticalDeviceFunction() throws Exception {
        ExpandableNotificationRow row =
                mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
        row.getEntry().channel.setImportanceLockedByCriticalDeviceFunction(true);

        assertTrue(row.getIsNonblockable());
    }
}
