Merge "Fix HUN launch animation." into sc-dev

This commit is contained in:
Jordan Demeulenaere
2021-04-30 07:25:44 +00:00
committed by Android (Google) Code Review
8 changed files with 155 additions and 114 deletions

View File

@@ -8,6 +8,7 @@ import android.app.PendingIntent
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.os.Looper
import android.os.RemoteException
import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
@@ -73,16 +74,20 @@ class ActivityLaunchAnimator(context: Context) {
* in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
* opening.
*
* If [controller] is null, then the intent will be started and no animation will run.
* If [controller] is null or [animate] is false, then the intent will be started and no
* animation will run.
*
* This method will throw any exception thrown by [intentStarter].
*/
@JvmOverloads
inline fun startIntentWithAnimation(
controller: Controller?,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
if (controller == null) {
if (controller == null || !animate) {
intentStarter(null)
controller?.callOnIntentStartedOnMainThread(willAnimate = false)
return
}
@@ -95,7 +100,7 @@ class ActivityLaunchAnimator(context: Context) {
val launchResult = intentStarter(animationAdapter)
val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS
runner.context.mainExecutor.execute { controller.onIntentStarted(willAnimate) }
controller.callOnIntentStartedOnMainThread(willAnimate)
// If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started.
@@ -104,17 +109,30 @@ class ActivityLaunchAnimator(context: Context) {
}
}
@PublishedApi
internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) {
this.getRootView().context.mainExecutor.execute {
this.onIntentStarted(willAnimate)
}
} else {
this.onIntentStarted(willAnimate)
}
}
/**
* Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
* [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
* for Java caller starting a [PendingIntent].
*/
@Throws(PendingIntent.CanceledException::class)
@JvmOverloads
fun startPendingIntentWithAnimation(
controller: Controller?,
animate: Boolean = true,
intentStarter: PendingIntentStarter
) {
startIntentWithAnimation(controller) { intentStarter.startPendingIntent(it) }
startIntentWithAnimation(controller, animate) { intentStarter.startPendingIntent(it) }
}
/** Create a new animation [Runner] controlled by [controller]. */

View File

@@ -6,6 +6,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
import kotlin.math.ceil
import kotlin.math.max
@@ -14,7 +15,8 @@ import kotlin.math.max
class NotificationLaunchAnimatorControllerProvider(
private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
private val notificationListContainer: NotificationListContainer,
private val depthController: NotificationShadeDepthController
private val depthController: NotificationShadeDepthController,
private val headsUpManager: HeadsUpManagerPhone
) {
fun getAnimatorController(
notification: ExpandableNotificationRow
@@ -23,7 +25,8 @@ class NotificationLaunchAnimatorControllerProvider(
notificationShadeWindowViewController,
notificationListContainer,
depthController,
notification
notification,
headsUpManager
)
}
}
@@ -37,8 +40,11 @@ class NotificationLaunchAnimatorController(
private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
private val notificationListContainer: NotificationListContainer,
private val depthController: NotificationShadeDepthController,
private val notification: ExpandableNotificationRow
private val notification: ExpandableNotificationRow,
private val headsUpManager: HeadsUpManagerPhone
) : ActivityLaunchAnimator.Controller {
private val notificationKey = notification.entry.sbn.key
override fun getRootView(): View = notification.rootView
override fun createAnimatorState(): ActivityLaunchAnimator.State {
@@ -76,12 +82,25 @@ class NotificationLaunchAnimatorController(
override fun onIntentStarted(willAnimate: Boolean) {
notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)
if (!willAnimate) {
removeHun(animate = true)
}
}
private fun removeHun(animate: Boolean) {
if (!headsUpManager.isAlerting(notificationKey)) {
return
}
headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate)
}
override fun onLaunchAnimationCancelled() {
// TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
// here?
notificationShadeWindowViewController.setExpandAnimationRunning(false)
removeHun(animate = true)
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -99,6 +118,7 @@ class NotificationLaunchAnimatorController(
notificationShadeWindowViewController.setExpandAnimationRunning(false)
notificationListContainer.setExpandingNotification(null)
applyParams(null)
removeHun(animate = false)
}
private fun applyParams(params: ExpandAnimationParameters?) {

View File

@@ -301,7 +301,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
///////////////////////////////////////////////////////////////////////////////////////////////
// HeadsUpManager public methods overrides:
// HeadsUpManager public methods overrides and overloads:
@Override
public boolean isTrackingHeadsUp() {
@@ -318,6 +318,18 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
mSwipedOutKeys.add(key);
}
public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
boolean animate) {
if (animate) {
return removeNotification(key, releaseImmediately);
} else {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
boolean removed = removeNotification(key, releaseImmediately);
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
return removed;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Dumpable overrides:

View File

@@ -1412,7 +1412,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(),
mNotificationShadeDepthControllerLazy.get()
mNotificationShadeDepthControllerLazy.get(),
mHeadsUpManager
);
// TODO: inject this.
@@ -2785,7 +2786,7 @@ public class StatusBar extends SystemUI implements DemoMode,
intent, mLockscreenUserManager.getCurrentUserId());
ActivityLaunchAnimator.Controller animController = null;
if (animationController != null && areLaunchAnimationsEnabled()) {
if (animationController != null) {
animController = dismissShade ? new StatusBarLaunchAnimatorController(
animationController, this, true /* isLaunchForActivity */)
: animationController;
@@ -2801,46 +2802,48 @@ public class StatusBar extends SystemUI implements DemoMode,
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(flags);
int[] result = new int[] { ActivityManager.START_CANCELED };
int[] result = new int[]{ActivityManager.START_CANCELED};
mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda, (adapter) -> {
ActivityOptions options = new ActivityOptions(
getActivityOptions(mDisplayId, adapter));
options.setDisallowEnterPictureInPictureWhileLaunching(
disallowEnterPictureInPictureWhileLaunching);
if (CameraIntents.isInsecureCameraIntent(intent)) {
// Normally an activity will set it's requested rotation
// animation on its window. However when launching an activity
// causes the orientation to change this is too late. In these cases
// the default animation is used. This doesn't look good for
// the camera (as it rotates the camera contents out of sync
// with physical reality). So, we ask the WindowManager to
// force the crossfade animation if an orientation change
// happens to occur during the launch.
options.setRotationAnimationHint(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
}
if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
// Settings Panel is implemented as activity(not a dialog), so
// underlying app is paused and may enter picture-in-picture mode
// as a result.
// So we need to disable picture-in-picture mode here
// if it is volume panel.
options.setDisallowEnterPictureInPictureWhileLaunching(true);
}
mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda,
areLaunchAnimationsEnabled(), (adapter) -> {
ActivityOptions options = new ActivityOptions(
getActivityOptions(mDisplayId, adapter));
options.setDisallowEnterPictureInPictureWhileLaunching(
disallowEnterPictureInPictureWhileLaunching);
if (CameraIntents.isInsecureCameraIntent(intent)) {
// Normally an activity will set it's requested rotation
// animation on its window. However when launching an activity
// causes the orientation to change this is too late. In these cases
// the default animation is used. This doesn't look good for
// the camera (as it rotates the camera contents out of sync
// with physical reality). So, we ask the WindowManager to
// force the crossfade animation if an orientation change
// happens to occur during the launch.
options.setRotationAnimationHint(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
}
if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
// Settings Panel is implemented as activity(not a dialog), so
// underlying app is paused and may enter picture-in-picture mode
// as a result.
// So we need to disable picture-in-picture mode here
// if it is volume panel.
options.setDisallowEnterPictureInPictureWhileLaunching(true);
}
try {
result[0] = ActivityTaskManager.getService().startActivityAsUser(
null, mContext.getBasePackageName(), mContext.getAttributionTag(),
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
options.toBundle(), UserHandle.CURRENT.getIdentifier());
} catch (RemoteException e) {
Log.w(TAG, "Unable to start activity", e);
}
return result[0];
});
try {
result[0] = ActivityTaskManager.getService().startActivityAsUser(
null, mContext.getBasePackageName(),
mContext.getAttributionTag(),
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
options.toBundle(), UserHandle.CURRENT.getIdentifier());
} catch (RemoteException e) {
Log.w(TAG, "Unable to start activity", e);
}
return result[0];
});
if (callback != null) {
callback.onActivityStarted(result[0]);
@@ -4559,19 +4562,17 @@ public class StatusBar extends SystemUI implements DemoMode,
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
boolean animate = animationController != null && areLaunchAnimationsEnabled();
boolean collapse = !animate;
boolean collapse = animationController == null;
executeActionDismissingKeyguard(() -> {
try {
// We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
// shade is collapsed after the animation (or when it is cancelled, aborted, etc).
ActivityLaunchAnimator.Controller controller =
animate ? new StatusBarLaunchAnimatorController(animationController, this,
intent.isActivity())
: null;
animationController != null ? new StatusBarLaunchAnimatorController(
animationController, this, intent.isActivity()) : null;
mActivityLaunchAnimator.startPendingIntentWithAnimation(
controller,
controller, areLaunchAnimationsEnabled(),
(animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
null, getActivityOptions(mDisplayId, animationAdapter)));
} catch (PendingIntent.CanceledException e) {

View File

@@ -283,7 +283,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
// TODO: Some of this code may be able to move to NotificationEntryManager.
removeHUN(row);
String key = row.getEntry().getSbn().getKey();
if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
// Release the HUN notification to the shade.
if (mPresenter.isPresenterFullyCollapsed()) {
HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
}
}
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
entry, row, controller, intent,
@@ -331,6 +337,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
// bypass work challenge
if (mStatusBarRemoteInputCallback.startWorkChallengeIfNecessary(userId,
intent.getIntentSender(), notificationKey)) {
removeHUN(row);
// Show work challenge, do not run PendingIntent and
// remove notification
collapseOnMainThread();
@@ -350,6 +357,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
final boolean canBubble = entry.canBubble();
if (canBubble) {
mLogger.logExpandingBubble(notificationKey);
removeHUN(row);
expandBubbleStackOnMainThread(entry);
} else {
startNotificationIntent(
@@ -422,14 +430,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
ActivityLaunchAnimator.Controller animationController = null;
if (!wasOccluded && mStatusBar.areLaunchAnimationsEnabled()) {
animationController = new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
isActivityIntent);
}
ActivityLaunchAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
isActivityIntent);
mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
!wasOccluded && mStatusBar.areLaunchAnimationsEnabled(),
(adapter) -> {
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
@@ -442,13 +449,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
});
// Note that other cases when we should still collapse (like activity already on top) is
// handled by the StatusBarLaunchAnimatorController.
boolean shouldCollapse = animationController == null;
if (shouldCollapse) {
collapseOnMainThread();
}
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -462,34 +462,19 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
ExpandableNotificationRow row) {
mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
ActivityLaunchAnimator.Controller animationController = null;
if (mStatusBar.areLaunchAnimationsEnabled()) {
animationController = new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
true /* isActivityIntent */);
}
ActivityLaunchAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row),
mStatusBar, true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation(
animationController,
animationController, mStatusBar.areLaunchAnimationsEnabled(),
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
mStatusBar.getDisplayId(),
adapter),
new UserHandle(UserHandle.getUserId(appUid))));
// Note that other cases when we should still collapse (like activity already on
// top) is handled by the StatusBarLaunchAnimatorController.
boolean shouldCollapse = animationController == null;
// Putting it back on the main thread, since we're touching views
mMainThreadHandler.post(() -> {
removeHUN(row);
if (shouldCollapse) {
mCommandQueue.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */);
}
});
});
return true;
}, null, false /* afterKeyguardGone */);
@@ -508,26 +493,16 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
tsb.addNextIntent(intent);
}
ActivityLaunchAnimator.Controller animationController = null;
if (mStatusBar.areLaunchAnimationsEnabled()) {
animationController = new StatusBarLaunchAnimatorController(
ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
true /* isActivityIntent */);
}
ActivityLaunchAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation(animationController,
mStatusBar.areLaunchAnimationsEnabled(),
(adapter) -> tsb.startActivities(
getActivityOptions(mStatusBar.getDisplayId(), adapter),
UserHandle.CURRENT));
// Note that other cases when we should still collapse (like activity already on
// top) is handled by the StatusBarLaunchAnimatorController.
boolean shouldCollapse = animationController == null;
if (shouldCollapse) {
// Putting it back on the main thread, since we're touching views
mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
}
});
return true;
}, null, false /* afterKeyguardGone */);
@@ -536,11 +511,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private void removeHUN(ExpandableNotificationRow row) {
String key = row.getEntry().getSbn().getKey();
if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
// Release the HUN notification to the shade.
if (mPresenter.isPresenterFullyCollapsed()) {
HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
}
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
// become canceled shortly by NoMan, but we can't assume that.
mHeadsUpManager.removeNotification(key, true /* releaseImmediately */);

View File

@@ -341,7 +341,6 @@ interface SmartActionInflater {
activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
smartReplyController
.smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
headsUpManager.removeNotification(entry.key, true /* releaseImmediately */)
}
}
}

View File

@@ -44,12 +44,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
private fun startIntentWithAnimation(
controller: ActivityLaunchAnimator.Controller? = this.controller,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
// We start in a new thread so that we can ensure that the callbacks are called in the main
// thread.
thread {
activityLaunchAnimator.startIntentWithAnimation(controller, intentStarter)
activityLaunchAnimator.startIntentWithAnimation(controller, animate, intentStarter)
}.join()
}
@@ -94,6 +95,16 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
assertFalse(willAnimateCaptor.value)
}
@Test
fun doesNotAnimateIfAnimateIsFalse() {
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
startIntentWithAnimation(animate = false) { ActivityManager.START_SUCCESS }
waitForIdleSync()
verify(controller).onIntentStarted(willAnimateCaptor.capture())
assertFalse(willAnimateCaptor.value)
}
@Test
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityLaunchAnimator.createRunner(controller)

View File

@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -73,6 +74,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -184,6 +186,14 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
when(mOnUserInteractionCallback.getGroupSummaryToDismiss(mNotificationRow.getEntry()))
.thenReturn(null);
HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
new NotificationLaunchAnimatorControllerProvider(
mock(NotificationShadeWindowViewController.class), mock(
NotificationListContainer.class),
mock(NotificationShadeDepthController.class),
headsUpManager);
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter.Builder(
getContext(),
@@ -192,7 +202,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mUiBgExecutor,
mEntryManager,
mNotifPipeline,
mock(HeadsUpManagerPhone.class),
headsUpManager,
mActivityStarter,
mClickNotifier,
mock(StatusBarStateController.class),
@@ -220,8 +230,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
.setNotificationPanelViewController(
mock(NotificationPanelViewController.class))
.setActivityLaunchAnimator(mActivityLaunchAnimator)
.setNotificationAnimatorControllerProvider(
mock(NotificationLaunchAnimatorControllerProvider.class))
.setNotificationAnimatorControllerProvider(notificationAnimationProvider)
.build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
@@ -259,7 +268,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
// Then
verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(eq(null), any());
verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
eq(false) /* animate */, any());
verify(mAssistManager).hideAssist();