diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 53d38c49ec9b9..f0e5462bc1678 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -29,18 +29,14 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams; -import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.NotificationPanelView; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowView; /** @@ -59,28 +55,28 @@ public class ActivityLaunchAnimator { private final NotificationPanelView mNotificationPanel; private final NotificationListContainer mNotificationContainer; private final StatusBarWindowView mStatusBarWindow; - private final StatusBarStateController mStatusBarStateController; - private StatusBar mStatusBar; + private Callback mCallback; private final Runnable mTimeoutRunnable = () -> { setAnimationPending(false); - mStatusBar.collapsePanel(true /* animate */); + mCallback.onExpandAnimationTimedOut(); }; private boolean mAnimationPending; + private boolean mAnimationRunning; + private boolean mIsLaunchForActivity; public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow, - StatusBar statusBar, + Callback callback, NotificationPanelView notificationPanel, NotificationListContainer container) { mNotificationPanel = notificationPanel; mNotificationContainer = container; mStatusBarWindow = statusBarWindow; - mStatusBar = statusBar; - mStatusBarStateController = Dependency.get(StatusBarStateController.class); + mCallback = callback; } public RemoteAnimationAdapter getLaunchAnimation( ExpandableNotificationRow sourceNotification, boolean occluded) { - if (mStatusBarStateController.getState() != StatusBarState.SHADE || occluded) { + if (!mCallback.areLaunchAnimationsEnabled() || occluded) { return null; } AnimationRunner animationRunner = new AnimationRunner(sourceNotification); @@ -92,10 +88,21 @@ public class ActivityLaunchAnimator { return mAnimationPending; } - public void setLaunchResult(int launchResult) { + /** + * Set the launch result the intent requested + * + * @param launchResult the launch result + * @param wasIntentActivity was this launch for an activity + */ + public void setLaunchResult(int launchResult, boolean wasIntentActivity) { + mIsLaunchForActivity = wasIntentActivity; setAnimationPending((launchResult == ActivityManager.START_TASK_TO_FRONT || launchResult == ActivityManager.START_SUCCESS) - && mStatusBarStateController.getState() == StatusBarState.SHADE); + && mCallback.areLaunchAnimationsEnabled()); + } + + public boolean isLaunchForActivity() { + return mIsLaunchForActivity; } private void setAnimationPending(boolean pending) { @@ -108,12 +115,16 @@ public class ActivityLaunchAnimator { } } + public boolean isAnimationRunning() { + return mAnimationRunning; + } + class AnimationRunner extends IRemoteAnimationRunner.Stub { private final ExpandableNotificationRow mSourceNotification; private final ExpandAnimationParameters mParams; private final Rect mWindowCrop = new Rect(); - private boolean mInstantCollapsePanel = true; + private boolean mIsFullScreenLaunch = true; private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier; public AnimationRunner(ExpandableNotificationRow sourceNofitication) { @@ -136,10 +147,10 @@ public class ActivityLaunchAnimator { } setExpandAnimationRunning(true); - mInstantCollapsePanel = primary.position.y == 0 + mIsFullScreenLaunch = primary.position.y == 0 && primary.sourceContainerBounds.height() >= mNotificationPanel.getHeight(); - if (!mInstantCollapsePanel) { + if (!mIsFullScreenLaunch) { mNotificationPanel.collapseWithDuration(ANIMATION_DURATION); } ValueAnimator anim = ValueAnimator.ofFloat(0, 1); @@ -192,9 +203,6 @@ public class ActivityLaunchAnimator { @Override public void onAnimationEnd(Animator animation) { setExpandAnimationRunning(false); - if (mInstantCollapsePanel) { - mStatusBar.collapsePanel(false /* animate */); - } invokeCallback(iRemoteAnimationFinishedCallback); } }); @@ -228,7 +236,9 @@ public class ActivityLaunchAnimator { mSourceNotification.setExpandAnimationRunning(running); mStatusBarWindow.setExpandAnimationRunning(running); mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null); + mAnimationRunning = running; if (!running) { + mCallback.onExpandAnimationFinished(mIsFullScreenLaunch); applyParamsToNotification(null); applyParamsToNotificationList(null); } @@ -257,7 +267,7 @@ public class ActivityLaunchAnimator { public void onAnimationCancelled() throws RemoteException { mSourceNotification.post(() -> { setAnimationPending(false); - mStatusBar.onLaunchAnimationCancelled(); + mCallback.onLaunchAnimationCancelled(); }); } }; @@ -319,4 +329,30 @@ public class ActivityLaunchAnimator { return startTranslationZ; } } + + public interface Callback { + + /** + * Called when the launch animation was cancelled. + */ + void onLaunchAnimationCancelled(); + + /** + * Called when the launch animation has timed out without starting an actual animation. + */ + void onExpandAnimationTimedOut(); + + /** + * Called when the expand animation has finished. + * + * @param launchIsFullScreen True if this launch was fullscreen, such that now the window + * fills the whole screen + */ + void onExpandAnimationFinished(boolean launchIsFullScreen); + + /** + * Are animations currently enabled. + */ + boolean areLaunchAnimationsEnabled(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 103781b7ae3cc..45bcc4e5c4150 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -255,7 +255,8 @@ public class StatusBar extends SystemUI implements DemoMode, ActivityStarter, OnUnlockMethodChangedListener, OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback, ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter, - StatusBarStateController.StateListener, AmbientPulseManager.OnAmbientChangedListener { + StatusBarStateController.StateListener, AmbientPulseManager.OnAmbientChangedListener, + ActivityLaunchAnimator.Callback { public static final boolean MULTIUSER_DEBUG = false; public static final boolean ENABLE_CHILD_NOTIFICATIONS @@ -1906,6 +1907,7 @@ public class StatusBar extends SystemUI implements DemoMode, } } + @Override public void onLaunchAnimationCancelled() { if (!isCollapsing()) { onClosingFinished(); @@ -1916,6 +1918,31 @@ public class StatusBar extends SystemUI implements DemoMode, return mHeadsUpAppearanceController.shouldBeVisible(); } + @Override + public void onExpandAnimationFinished(boolean launchIsFullScreen) { + if (!isCollapsing()) { + onClosingFinished(); + } + if (launchIsFullScreen) { + instantCollapseNotificationPanel(); + } + } + + @Override + public void onExpandAnimationTimedOut() { + if (isPresenterFullyCollapsed() && !isCollapsing() + && !mActivityLaunchAnimator.isLaunchForActivity()) { + onClosingFinished(); + } else { + collapsePanel(true /* animate */); + } + } + + @Override + public boolean areLaunchAnimationsEnabled() { + return mState == StatusBarState.SHADE; + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ @@ -3353,7 +3380,9 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean isCollapsing() { - return mNotificationPanel.isCollapsing() || mActivityLaunchAnimator.isAnimationPending(); + return mNotificationPanel.isCollapsing() + || mActivityLaunchAnimator.isAnimationPending() + || mActivityLaunchAnimator.isAnimationRunning(); } public void addPostCollapseAction(Runnable r) { @@ -4738,7 +4767,8 @@ public class StatusBar extends SystemUI implements DemoMode, : notification.fullScreenIntent; final String notificationKey = sbn.getKey(); - final boolean afterKeyguardGone = intent.isActivity() + boolean isActivityIntent = intent.isActivity(); + final boolean afterKeyguardGone = isActivityIntent && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), mLockscreenUserManager.getCurrentUserId()); final boolean wasOccluded = mIsOccluded; @@ -4779,7 +4809,7 @@ public class StatusBar extends SystemUI implements DemoMode, // If we are launching a work activity and require to launch // separate work challenge, we defer the activity action and cancel // notification until work challenge is unlocked. - if (intent.isActivity()) { + if (isActivityIntent) { final int userId = intent.getCreatorUserHandle().getIdentifier(); if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) && mKeyguardManager.isDeviceLocked(userId)) { @@ -4815,7 +4845,7 @@ public class StatusBar extends SystemUI implements DemoMode, } launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, null, null, getActivityOptions(adapter)); - mActivityLaunchAnimator.setLaunchResult(launchResult); + mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent); } catch (RemoteException | PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. // Just log the exception message. @@ -4823,7 +4853,7 @@ public class StatusBar extends SystemUI implements DemoMode, // TODO: Dismiss Keyguard. } - if (intent.isActivity()) { + if (isActivityIntent) { mAssistManager.hideAssist(); } } @@ -4942,7 +4972,7 @@ public class StatusBar extends SystemUI implements DemoMode, .startActivities(getActivityOptions( mActivityLaunchAnimator.getLaunchAnimation(row, mIsOccluded)), new UserHandle(UserHandle.getUserId(appUid))); - mActivityLaunchAnimator.setLaunchResult(launchResult); + mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */); if (shouldCollapse()) { // Putting it back on the main thread, since we're touching views mStatusBarWindow.post(() -> animateCollapsePanels( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java new file mode 100644 index 0000000000000..435ede4d8ff0b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java @@ -0,0 +1,116 @@ +/* + * 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.notification; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.view.RemoteAnimationAdapter; +import android.view.View; +import android.widget.FrameLayout; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.NotificationPanelView; +import com.android.systemui.statusbar.phone.StatusBarWindowView; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.stubbing.Answer; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ActivityLaunchAnimatorTest extends SysuiTestCase { + + private ActivityLaunchAnimator mLaunchAnimator; + private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class); + private StatusBarWindowView mStatusBarWindowView = mock(StatusBarWindowView.class); + private NotificationListContainer mNotificationContainer + = mock(NotificationListContainer.class); + private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class); + + @Before + public void setUp() throws Exception { + when(mCallback.areLaunchAnimationsEnabled()).thenReturn(true); + mLaunchAnimator = new ActivityLaunchAnimator( + mStatusBarWindowView, + mCallback, + mock(NotificationPanelView.class), + mNotificationContainer); + + } + + @Test + public void testReturnsNullIfNotEnabled() { + when(mCallback.areLaunchAnimationsEnabled()).thenReturn(false); + RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow, + false /* occluded */); + Assert.assertTrue("The LaunchAnimator generated an animation even though animations are " + + "disabled", launchAnimation == null); + } + + @Test + public void testNotWorkingWhenOccluded() { + when(mCallback.areLaunchAnimationsEnabled()).thenReturn(false); + RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow, + true /* occluded */); + Assert.assertTrue("The LaunchAnimator generated an animation even though we're occluded", + launchAnimation == null); + } + + @Test + public void testTimeoutCalled() { + RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow, + false /* occluded */); + Assert.assertTrue("No animation generated", launchAnimation != null); + executePostsImmediately(mStatusBarWindowView); + mLaunchAnimator.setLaunchResult(ActivityManager.START_SUCCESS, + true /* wasIntentActivity */); + verify(mCallback).onExpandAnimationTimedOut(); + } + + private void executePostsImmediately(View view) { + doAnswer((i) -> { + Runnable run = i.getArgument(0); + run.run(); + return null; + }).when(view).post(any()); + doAnswer((i) -> { + Runnable run = i.getArgument(0); + run.run(); + return null; + }).when(view).postDelayed(any(), anyLong()); + } +} +