Break strong ref to ExpandableNotificationRow
Since AnimationRunner requires two GCs to be collected, one in the binding process and then one in system ui, the source notification can be retained for longer than expected. To avoid this, break the strong reference to the source notification after the animation has finished or is cancelled. Bug: 144109427 Test: Dumped java heap after clicking on notification to open app. Checked that ExpandableNotificationRow wasn't retained by AnimationRunner. Change-Id: Id43847e3d10d9d96adf2f7af1781b6978f9de975
This commit is contained in:
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
@@ -41,6 +42,8 @@ import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
|
||||
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
|
||||
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A class that allows activities to be launched in a seamless way where the notification
|
||||
* transforms nicely into the starting window.
|
||||
@@ -59,6 +62,7 @@ public class ActivityLaunchAnimator {
|
||||
private final float mWindowCornerRadius;
|
||||
private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
|
||||
private final NotificationShadeDepthController mDepthController;
|
||||
private final Executor mMainExecutor;
|
||||
private Callback mCallback;
|
||||
private final Runnable mTimeoutRunnable = () -> {
|
||||
setAnimationPending(false);
|
||||
@@ -73,12 +77,14 @@ public class ActivityLaunchAnimator {
|
||||
Callback callback,
|
||||
NotificationPanelViewController notificationPanel,
|
||||
NotificationShadeDepthController depthController,
|
||||
NotificationListContainer container) {
|
||||
NotificationListContainer container,
|
||||
Executor mainExecutor) {
|
||||
mNotificationPanel = notificationPanel;
|
||||
mNotificationContainer = container;
|
||||
mDepthController = depthController;
|
||||
mNotificationShadeWindowViewController = notificationShadeWindowViewController;
|
||||
mCallback = callback;
|
||||
mMainExecutor = mainExecutor;
|
||||
mWindowCornerRadius = ScreenDecorationsUtils
|
||||
.getWindowCornerRadius(mNotificationShadeWindowViewController.getView()
|
||||
.getResources());
|
||||
@@ -91,7 +97,7 @@ public class ActivityLaunchAnimator {
|
||||
return null;
|
||||
}
|
||||
AnimationRunner animationRunner = new AnimationRunner(
|
||||
(ExpandableNotificationRow) sourceView);
|
||||
(ExpandableNotificationRow) sourceView, mMainExecutor);
|
||||
return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION,
|
||||
ANIMATION_DURATION - 150 /* statusBarTransitionDelay */);
|
||||
}
|
||||
@@ -134,17 +140,18 @@ public class ActivityLaunchAnimator {
|
||||
|
||||
class AnimationRunner extends IRemoteAnimationRunner.Stub {
|
||||
|
||||
private final ExpandableNotificationRow mSourceNotification;
|
||||
private final ExpandAnimationParameters mParams;
|
||||
private final ExpandAnimationParameters mParams = new ExpandAnimationParameters();
|
||||
private final Rect mWindowCrop = new Rect();
|
||||
private final float mNotificationCornerRadius;
|
||||
private final Executor mMainExecutor;
|
||||
@Nullable private ExpandableNotificationRow mSourceNotification;
|
||||
@Nullable private SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier;
|
||||
private float mCornerRadius;
|
||||
private boolean mIsFullScreenLaunch = true;
|
||||
private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier;
|
||||
|
||||
public AnimationRunner(ExpandableNotificationRow sourceNofitication) {
|
||||
mSourceNotification = sourceNofitication;
|
||||
mParams = new ExpandAnimationParameters();
|
||||
AnimationRunner(ExpandableNotificationRow sourceNotification, Executor mainExecutor) {
|
||||
mMainExecutor = mainExecutor;
|
||||
mSourceNotification = sourceNotification;
|
||||
mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification);
|
||||
mNotificationCornerRadius = Math.max(mSourceNotification.getCurrentTopRoundness(),
|
||||
mSourceNotification.getCurrentBottomRoundness());
|
||||
@@ -155,13 +162,15 @@ public class ActivityLaunchAnimator {
|
||||
RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
|
||||
IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
|
||||
throws RemoteException {
|
||||
mSourceNotification.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget(
|
||||
remoteAnimationTargets);
|
||||
if (primary == null) {
|
||||
if (primary == null || mSourceNotification == null) {
|
||||
setAnimationPending(false);
|
||||
invokeCallback(iRemoteAnimationFinishedCallback);
|
||||
mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
|
||||
mSourceNotification = null;
|
||||
mSyncRtTransactionApplier = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,28 +181,14 @@ public class ActivityLaunchAnimator {
|
||||
if (!mIsFullScreenLaunch) {
|
||||
mNotificationPanel.collapseWithDuration(ANIMATION_DURATION);
|
||||
}
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||
mParams.startPosition = mSourceNotification.getLocationOnScreen();
|
||||
mParams.startTranslationZ = mSourceNotification.getTranslationZ();
|
||||
mParams.startClipTopAmount = mSourceNotification.getClipTopAmount();
|
||||
if (mSourceNotification.isChildInGroup()) {
|
||||
int parentClip = mSourceNotification
|
||||
.getNotificationParent().getClipTopAmount();
|
||||
mParams.parentStartClipTopAmount = parentClip;
|
||||
// We need to calculate how much the child is clipped by the parent
|
||||
// because children always have 0 clipTopAmount
|
||||
if (parentClip != 0) {
|
||||
float childClip = parentClip
|
||||
- mSourceNotification.getTranslationY();
|
||||
if (childClip > 0.0f) {
|
||||
mParams.startClipTopAmount = (int) Math.ceil(childClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
int targetWidth = primary.sourceContainerBounds.width();
|
||||
int notificationHeight = mSourceNotification.getActualHeight()
|
||||
mParams.initFrom(mSourceNotification);
|
||||
final int targetWidth = primary.sourceContainerBounds.width();
|
||||
final int notificationHeight;
|
||||
final int notificationWidth;
|
||||
notificationHeight = mSourceNotification.getActualHeight()
|
||||
- mSourceNotification.getClipBottomAmount();
|
||||
int notificationWidth = mSourceNotification.getWidth();
|
||||
notificationWidth = mSourceNotification.getWidth();
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||
anim.setDuration(ANIMATION_DURATION);
|
||||
anim.setInterpolator(Interpolators.LINEAR);
|
||||
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@@ -231,6 +226,11 @@ public class ActivityLaunchAnimator {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ExpandableNotificationRow getRow() {
|
||||
return mSourceNotification;
|
||||
}
|
||||
|
||||
private void invokeCallback(IRemoteAnimationFinishedCallback callback) {
|
||||
try {
|
||||
callback.onAnimationFinished();
|
||||
@@ -253,7 +253,9 @@ public class ActivityLaunchAnimator {
|
||||
|
||||
private void setExpandAnimationRunning(boolean running) {
|
||||
mNotificationPanel.setLaunchingNotification(running);
|
||||
mSourceNotification.setExpandAnimationRunning(running);
|
||||
if (mSourceNotification != null) {
|
||||
mSourceNotification.setExpandAnimationRunning(running);
|
||||
}
|
||||
mNotificationShadeWindowViewController.setExpandAnimationRunning(running);
|
||||
mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
|
||||
mAnimationRunning = running;
|
||||
@@ -261,6 +263,8 @@ public class ActivityLaunchAnimator {
|
||||
mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
|
||||
applyParamsToNotification(null);
|
||||
applyParamsToNotificationShade(null);
|
||||
mSourceNotification = null;
|
||||
mSyncRtTransactionApplier = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -272,7 +276,9 @@ public class ActivityLaunchAnimator {
|
||||
}
|
||||
|
||||
private void applyParamsToNotification(ExpandAnimationParameters params) {
|
||||
mSourceNotification.applyExpandAnimationParams(params);
|
||||
if (mSourceNotification != null) {
|
||||
mSourceNotification.applyExpandAnimationParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyParamsToWindow(RemoteAnimationTarget app) {
|
||||
@@ -287,14 +293,18 @@ public class ActivityLaunchAnimator {
|
||||
.withCornerRadius(mCornerRadius)
|
||||
.withVisibility(true)
|
||||
.build();
|
||||
mSyncRtTransactionApplier.scheduleApply(true /* earlyWakeup */, params);
|
||||
if (mSyncRtTransactionApplier != null) {
|
||||
mSyncRtTransactionApplier.scheduleApply(true /* earlyWakeup */, params);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancelled() throws RemoteException {
|
||||
mSourceNotification.post(() -> {
|
||||
mMainExecutor.execute(() -> {
|
||||
setAnimationPending(false);
|
||||
mCallback.onLaunchAnimationCancelled();
|
||||
mSourceNotification = null;
|
||||
mSyncRtTransactionApplier = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -359,6 +369,28 @@ public class ActivityLaunchAnimator {
|
||||
public float getStartTranslationZ() {
|
||||
return startTranslationZ;
|
||||
}
|
||||
|
||||
/** Initialize with data pulled from the row. */
|
||||
void initFrom(@Nullable ExpandableNotificationRow row) {
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
startPosition = row.getLocationOnScreen();
|
||||
startTranslationZ = row.getTranslationZ();
|
||||
startClipTopAmount = row.getClipTopAmount();
|
||||
if (row.isChildInGroup()) {
|
||||
int parentClip = row.getNotificationParent().getClipTopAmount();
|
||||
parentStartClipTopAmount = parentClip;
|
||||
// We need to calculate how much the child is clipped by the parent
|
||||
// because children always have 0 clipTopAmount
|
||||
if (parentClip != 0) {
|
||||
float childClip = parentClip - row.getTranslationY();
|
||||
if (childClip > 0.0f) {
|
||||
startClipTopAmount = (int) Math.ceil(childClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
||||
@@ -140,6 +140,7 @@ import com.android.systemui.bubbles.BubbleController;
|
||||
import com.android.systemui.charging.WirelessChargingAnimation;
|
||||
import com.android.systemui.classifier.FalsingLog;
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dagger.qualifiers.UiBackground;
|
||||
import com.android.systemui.fragments.ExtensionFragmentListener;
|
||||
import com.android.systemui.fragments.FragmentHostManager;
|
||||
@@ -467,6 +468,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
private final ScrimController mScrimController;
|
||||
protected DozeScrimController mDozeScrimController;
|
||||
private final Executor mUiBgExecutor;
|
||||
private final Executor mMainExecutor;
|
||||
|
||||
protected boolean mDozing;
|
||||
|
||||
@@ -625,6 +627,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
DisplayMetrics displayMetrics,
|
||||
MetricsLogger metricsLogger,
|
||||
@UiBackground Executor uiBgExecutor,
|
||||
@Main Executor mainExecutor,
|
||||
NotificationMediaManager notificationMediaManager,
|
||||
NotificationLockscreenUserManager lockScreenUserManager,
|
||||
NotificationRemoteInputManager remoteInputManager,
|
||||
@@ -705,6 +708,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
mDisplayMetrics = displayMetrics;
|
||||
mMetricsLogger = metricsLogger;
|
||||
mUiBgExecutor = uiBgExecutor;
|
||||
mMainExecutor = mainExecutor;
|
||||
mMediaManager = notificationMediaManager;
|
||||
mLockscreenUserManager = lockScreenUserManager;
|
||||
mRemoteInputManager = remoteInputManager;
|
||||
@@ -1232,7 +1236,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
mActivityLaunchAnimator = new ActivityLaunchAnimator(
|
||||
mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
|
||||
mNotificationShadeDepthControllerLazy.get(),
|
||||
(NotificationListContainer) mStackScroller);
|
||||
(NotificationListContainer) mStackScroller,
|
||||
mMainExecutor);
|
||||
|
||||
// TODO: inject this.
|
||||
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.android.systemui.assist.AssistManager;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.bubbles.BubbleController;
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dagger.qualifiers.UiBackground;
|
||||
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
@@ -145,6 +146,7 @@ public interface StatusBarPhoneModule {
|
||||
DisplayMetrics displayMetrics,
|
||||
MetricsLogger metricsLogger,
|
||||
@UiBackground Executor uiBgExecutor,
|
||||
@Main Executor mainExecutor,
|
||||
NotificationMediaManager notificationMediaManager,
|
||||
NotificationLockscreenUserManager lockScreenUserManager,
|
||||
NotificationRemoteInputManager remoteInputManager,
|
||||
@@ -224,6 +226,7 @@ public interface StatusBarPhoneModule {
|
||||
displayMetrics,
|
||||
metricsLogger,
|
||||
uiBgExecutor,
|
||||
mainExecutor,
|
||||
notificationMediaManager,
|
||||
lockScreenUserManager,
|
||||
remoteInputManager,
|
||||
|
||||
@@ -19,14 +19,18 @@ package com.android.systemui.statusbar.notification;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.os.RemoteException;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.IRemoteAnimationFinishedCallback;
|
||||
import android.view.RemoteAnimationAdapter;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
@@ -36,6 +40,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
|
||||
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
|
||||
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
|
||||
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
@@ -68,9 +74,11 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase {
|
||||
private NotificationPanelViewController mNotificationPanelViewController;
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
private FakeExecutor mExecutor;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
when(mNotificationShadeWindowViewController.getView())
|
||||
.thenReturn(mNotificationShadeWindowView);
|
||||
when(mNotificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
|
||||
@@ -80,8 +88,8 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase {
|
||||
mCallback,
|
||||
mNotificationPanelViewController,
|
||||
mNotificationShadeDepthController,
|
||||
mNotificationContainer);
|
||||
|
||||
mNotificationContainer,
|
||||
mExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,6 +121,29 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase {
|
||||
verify(mCallback).onExpandAnimationTimedOut();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowLinkBrokenOnAnimationStartFail() throws RemoteException {
|
||||
ActivityLaunchAnimator.AnimationRunner runner = mLaunchAnimator.new AnimationRunner(mRow,
|
||||
mExecutor);
|
||||
// WHEN onAnimationStart with no valid remote target
|
||||
runner.onAnimationStart(new RemoteAnimationTarget[0], new RemoteAnimationTarget[0],
|
||||
mock(IRemoteAnimationFinishedCallback.class));
|
||||
mExecutor.runAllReady();
|
||||
// THEN the row is nulled out so that it won't be retained
|
||||
Assert.assertTrue("The row should be null", runner.getRow() == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowLinkBrokenOnAnimationCancelled() throws RemoteException {
|
||||
ActivityLaunchAnimator.AnimationRunner runner = mLaunchAnimator.new AnimationRunner(mRow,
|
||||
mExecutor);
|
||||
// WHEN onAnimationCancelled
|
||||
runner.onAnimationCancelled();
|
||||
mExecutor.runAllReady();
|
||||
// THEN the row is nulled out so that it won't be retained
|
||||
Assert.assertTrue("The row should be null", runner.getRow() == null);
|
||||
}
|
||||
|
||||
private void executePostsImmediately(View view) {
|
||||
doAnswer((i) -> {
|
||||
Runnable run = i.getArgument(0);
|
||||
|
||||
@@ -251,6 +251,7 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
@Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
|
||||
private ShadeController mShadeController;
|
||||
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
private FakeExecutor mMainExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
private InitController mInitController = new InitController();
|
||||
|
||||
@Before
|
||||
@@ -353,6 +354,7 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
new DisplayMetrics(),
|
||||
mMetricsLogger,
|
||||
mUiBgExecutor,
|
||||
mMainExecutor,
|
||||
mNotificationMediaManager,
|
||||
mLockscreenUserManager,
|
||||
mRemoteInputManager,
|
||||
|
||||
Reference in New Issue
Block a user