Merge "Updated NotificationPanelViewController to listen to animateExpandNotificationsPanel and animateCollapsePanels." into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-06-19 22:40:06 +00:00
committed by Android (Google) Code Review
3 changed files with 359 additions and 1 deletions

View File

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

View File

@@ -0,0 +1,150 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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);
}
}