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

Commit 489dd715 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Updated NotificationPanelViewController to listen to...

Merge "Updated NotificationPanelViewController to listen to animateExpandNotificationsPanel and animateCollapsePanels." into rvc-dev
parents 9d431fbc 44a01849
Loading
Loading
Loading
Loading
+70 −1
Original line number Diff line number Diff line
@@ -48,17 +48,21 @@ import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.car.window.OverlayPanelViewController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;

import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Singleton;

/** View controller for the notification panel. */
@Singleton
public class NotificationPanelViewController extends OverlayPanelViewController {
public class NotificationPanelViewController extends OverlayPanelViewController
        implements CommandQueue.Callbacks {

    private static final boolean DEBUG = true;
    private static final String TAG = "NotificationPanelViewController";
@@ -68,12 +72,14 @@ public class NotificationPanelViewController extends OverlayPanelViewController
    private final CarServiceProvider mCarServiceProvider;
    private final IStatusBarService mBarService;
    private final CommandQueue mCommandQueue;
    private final Executor mUiBgExecutor;
    private final NotificationDataManager mNotificationDataManager;
    private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
    private final CarNotificationListener mCarNotificationListener;
    private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
    private final StatusBarStateController mStatusBarStateController;
    private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
    private final NotificationVisibilityLogger mNotificationVisibilityLogger;

    private float mInitialBackgroundAlpha;
    private float mBackgroundAlphaDiff;
@@ -98,6 +104,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
            @Main Resources resources,
            OverlayViewGlobalStateController overlayViewGlobalStateController,
            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
            @UiBackground Executor uiBgExecutor,

            /* Other things */
            CarServiceProvider carServiceProvider,
@@ -110,6 +117,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
            CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
            CarNotificationListener carNotificationListener,
            NotificationClickHandlerFactory notificationClickHandlerFactory,
            NotificationVisibilityLogger notificationVisibilityLogger,

            /* Things that need to be replaced */
            StatusBarStateController statusBarStateController
@@ -121,12 +129,15 @@ public class NotificationPanelViewController extends OverlayPanelViewController
        mCarServiceProvider = carServiceProvider;
        mBarService = barService;
        mCommandQueue = commandQueue;
        mUiBgExecutor = uiBgExecutor;
        mNotificationDataManager = notificationDataManager;
        mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
        mCarNotificationListener = carNotificationListener;
        mNotificationClickHandlerFactory = notificationClickHandlerFactory;
        mStatusBarStateController = statusBarStateController;
        mNotificationVisibilityLogger = notificationVisibilityLogger;

        mCommandQueue.addCallback(this);
        // Notification background setup.
        mInitialBackgroundAlpha = (float) mResources.getInteger(
                R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -151,11 +162,35 @@ public class NotificationPanelViewController extends OverlayPanelViewController
                        .config_enableHeadsUpNotificationWhenNotificationShadeOpen);
    }

    // CommandQueue.Callbacks

    @Override
    public void animateExpandNotificationsPanel() {
        if (!isPanelExpanded()) {
            toggle();
        }
    }

    @Override
    public void animateCollapsePanels(int flags, boolean force) {
        if (isPanelExpanded()) {
            toggle();
        }
    }

    // OverlayViewController

    @Override
    protected void onFinishInflate() {
        reinflate();
    }

    @Override
    protected void hideInternal() {
        super.hideInternal();
        mNotificationVisibilityLogger.stop();
    }

    @Override
    protected boolean shouldShowNavigationBar() {
        return true;
@@ -197,6 +232,11 @@ public class NotificationPanelViewController extends OverlayPanelViewController
                mUnseenCountUpdateListener.onUnseenCountUpdate(
                        mNotificationDataManager.getUnseenNotificationCount());
            }
            mCarNotificationListener.setNotificationsShown(
                    mNotificationDataManager.getSeenNotifications());
            // This logs both when the notification panel is expanded and when the notification
            // panel is scrolled.
            mNotificationVisibilityLogger.log(isPanelExpanded());
        });

        mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
@@ -332,6 +372,8 @@ public class NotificationPanelViewController extends OverlayPanelViewController
        mNotificationDataManager.clearAll();
    }

    // OverlayPanelViewController

    @Override
    protected boolean shouldAnimateCollapsePanel() {
        return true;
@@ -363,6 +405,30 @@ public class NotificationPanelViewController extends OverlayPanelViewController
        mNotificationView.setVisibleNotificationsAsSeen();
    }

    @Override
    protected void onPanelVisible(boolean visible) {
        super.onPanelVisible(visible);
        mUiBgExecutor.execute(() -> {
            try {
                if (visible) {
                    // When notification panel is open even just a bit, we want to clear
                    // notification effects.
                    boolean clearNotificationEffects =
                            mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
                    mBarService.onPanelRevealed(clearNotificationEffects,
                            mNotificationDataManager.getVisibleNotifications().size());
                } else {
                    mBarService.onPanelHidden();
                }
            } catch (RemoteException ex) {
                // Won't fail unless the world has ended.
                Log.e(TAG, String.format(
                        "Unable to notify StatusBarService of panel visibility: %s", visible));
            }
        });

    }

    @Override
    protected void onPanelExpanded(boolean expand) {
        super.onPanelExpanded(expand);
@@ -373,6 +439,9 @@ public class NotificationPanelViewController extends OverlayPanelViewController
            }
            clearNotificationEffects();
        }
        if (!expand) {
            mNotificationVisibilityLogger.log(isPanelExpanded());
        }
    }

    /**
+150 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.car.notification;

import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;

import com.android.car.notification.AlertEntry;
import com.android.car.notification.NotificationDataManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.dagger.qualifiers.UiBackground;

import java.util.Set;
import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * Handles notification logging, in particular, logging which notifications are visible and which
 * are not.
 */
@Singleton
public class NotificationVisibilityLogger {

    private static final String TAG = "NotificationVisibilityLogger";

    private final ArraySet<NotificationVisibility> mCurrentlyVisible = new ArraySet<>();
    private final ArraySet<NotificationVisibility> mNewlyVisible = new ArraySet<>();
    private final ArraySet<NotificationVisibility> mPreviouslyVisible = new ArraySet<>();
    private final ArraySet<NotificationVisibility> mTmpCurrentlyVisible = new ArraySet<>();

    private final IStatusBarService mBarService;
    private final Executor mUiBgExecutor;
    private final NotificationDataManager mNotificationDataManager;

    private boolean mIsVisible;

    private final Runnable mVisibilityReporter = new Runnable() {

        @Override
        public void run() {
            if (mIsVisible) {
                int count = mNotificationDataManager.getVisibleNotifications().size();
                for (AlertEntry alertEntry : mNotificationDataManager.getVisibleNotifications()) {
                    NotificationVisibility visObj = NotificationVisibility.obtain(
                            alertEntry.getKey(),
                            /* rank= */ -1,
                            count,
                            mIsVisible,
                            NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA);
                    mTmpCurrentlyVisible.add(visObj);
                    if (!mCurrentlyVisible.contains(visObj)) {
                        mNewlyVisible.add(visObj);
                    }
                }
            }
            mPreviouslyVisible.addAll(mCurrentlyVisible);
            mPreviouslyVisible.removeAll(mTmpCurrentlyVisible);
            onNotificationVisibilityChanged(mNewlyVisible, mPreviouslyVisible);

            recycleAllVisibilityObjects(mCurrentlyVisible);
            mCurrentlyVisible.addAll(mTmpCurrentlyVisible);

            recycleAllVisibilityObjects(mPreviouslyVisible);
            recycleAllVisibilityObjects(mNewlyVisible);
            recycleAllVisibilityObjects(mTmpCurrentlyVisible);
        }
    };

    @Inject
    public NotificationVisibilityLogger(
            @UiBackground Executor uiBgExecutor,
            IStatusBarService barService,
            NotificationDataManager notificationDataManager) {
        mUiBgExecutor = uiBgExecutor;
        mBarService = barService;
        mNotificationDataManager = notificationDataManager;
    }

    /** Triggers a visibility report update to be sent to StatusBarService. */
    public void log(boolean isVisible) {
        mIsVisible = isVisible;
        mUiBgExecutor.execute(mVisibilityReporter);
    }

    /** Stops logging, clearing all visibility objects. */
    public void stop() {
        recycleAllVisibilityObjects(mCurrentlyVisible);
    }

    /**
     * Notify StatusBarService of change in notifications' visibility.
     */
    private void onNotificationVisibilityChanged(
            Set<NotificationVisibility> newlyVisible, Set<NotificationVisibility> noLongerVisible) {
        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
            return;
        }

        try {
            mBarService.onNotificationVisibilityChanged(
                    cloneVisibilitiesAsArr(newlyVisible), cloneVisibilitiesAsArr(noLongerVisible));
        } catch (RemoteException e) {
            // Won't fail unless the world has ended.
            Log.e(TAG, "Failed to notify StatusBarService of notification visibility change");
        }
    }

    /**
     * Clears array and recycles NotificationVisibility objects for reuse.
     */
    private static void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
        for (int i = 0; i < array.size(); i++) {
            array.valueAt(i).recycle();
        }
        array.clear();
    }

    /**
     * Converts Set of NotificationVisibility objects to primitive array.
     */
    private static NotificationVisibility[] cloneVisibilitiesAsArr(Set<NotificationVisibility> c) {
        NotificationVisibility[] array = new NotificationVisibility[c.size()];
        int i = 0;
        for (NotificationVisibility nv : c) {
            if (nv != null) {
                array[i] = nv.clone();
            }
            i++;
        }
        return array;
    }
}
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.car.notification;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Notification;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import androidx.test.filters.SmallTest;

import com.android.car.notification.AlertEntry;
import com.android.car.notification.NotificationDataManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Collections;

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

    private static final String PKG = "package_1";
    private static final String OP_PKG = "OpPackage";
    private static final int ID = 1;
    private static final String TAG = "Tag";
    private static final int UID = 2;
    private static final int INITIAL_PID = 3;
    private static final String CHANNEL_ID = "CHANNEL_ID";
    private static final String CONTENT_TITLE = "CONTENT_TITLE";
    private static final String OVERRIDE_GROUP_KEY = "OVERRIDE_GROUP_KEY";
    private static final long POST_TIME = 12345L;
    private static final UserHandle USER_HANDLE = new UserHandle(12);

    @Mock
    private IStatusBarService mBarService;
    @Mock
    private NotificationDataManager mNotificationDataManager;

    private NotificationVisibilityLogger mNotificationVisibilityLogger;
    private FakeExecutor mUiBgExecutor;
    private AlertEntry mMessageNotification;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(/* testClass= */this);

        mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
        Notification.Builder mNotificationBuilder1 = new Notification.Builder(mContext, CHANNEL_ID)
                .setContentTitle(CONTENT_TITLE);
        mMessageNotification = new AlertEntry(new StatusBarNotification(PKG, OP_PKG,
                ID, TAG, UID, INITIAL_PID, mNotificationBuilder1.build(), USER_HANDLE,
                OVERRIDE_GROUP_KEY, POST_TIME));

        when(mNotificationDataManager.getVisibleNotifications()).thenReturn(
                Collections.singletonList(mMessageNotification));

        mNotificationVisibilityLogger = new NotificationVisibilityLogger(
                mUiBgExecutor, mBarService, mNotificationDataManager);
    }

    @Test
    public void log_notifiesStatusBarService() throws RemoteException {
        mNotificationVisibilityLogger.log(/* isVisible= */ true);
        mUiBgExecutor.runNextReady();

        verify(mBarService).onNotificationVisibilityChanged(
                any(NotificationVisibility[].class), any(NotificationVisibility[].class));
    }

    @Test
    public void log_isVisibleIsTrue_notifiesOfNewlyVisibleItems() throws RemoteException {
        ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
                ArgumentCaptor.forClass(NotificationVisibility[].class);
        ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
                ArgumentCaptor.forClass(NotificationVisibility[].class);

        mNotificationVisibilityLogger.log(/* isVisible= */ true);
        mUiBgExecutor.runNextReady();

        verify(mBarService).onNotificationVisibilityChanged(
                newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
        assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(1);
        assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(0);
    }

    @Test
    public void log_isVisibleIsFalse_notifiesOfPreviouslyVisibleItems() throws RemoteException {
        ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
                ArgumentCaptor.forClass(NotificationVisibility[].class);
        ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
                ArgumentCaptor.forClass(NotificationVisibility[].class);
        mNotificationVisibilityLogger.log(/* isVisible= */ true);
        mUiBgExecutor.runNextReady();
        reset(mBarService);

        mNotificationVisibilityLogger.log(/* isVisible= */ false);
        mUiBgExecutor.runNextReady();

        verify(mBarService).onNotificationVisibilityChanged(
                newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
        assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(1);
        assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(0);
    }
}