Merge "Animate the Wallet activity launch on lockscreen (1/2)" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-08-19 14:02:41 +00:00
committed by Android (Google) Code Review
12 changed files with 180 additions and 45 deletions

View File

@@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.AppGlobals
import android.app.PendingIntent
import android.app.TaskInfo
import android.content.Context
@@ -44,6 +43,7 @@ class ActivityLaunchAnimator(
context: Context
) {
companion object {
private const val DEBUG = false
const val ANIMATION_DURATION = 500L
private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
@@ -75,8 +75,6 @@ class ActivityLaunchAnimator(
}
}
private val packageManager = AppGlobals.getPackageManager()
/** The interpolator used for the width, height, Y position and corner radius. */
private val animationInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.launch_animation_interpolator_y)
@@ -100,6 +98,10 @@ class ActivityLaunchAnimator(
* If possible, you should pass the [packageName] of the intent that will be started so that
* trampoline activity launches will also be animated.
*
* If the device is currently locked, the user will have to unlock it before the intent is
* started unless [showOverLockscreen] is true. In that case, the activity will be started
* directly over the lockscreen.
*
* This method will throw any exception thrown by [intentStarter].
*/
@JvmOverloads
@@ -107,21 +109,22 @@ class ActivityLaunchAnimator(
controller: Controller?,
animate: Boolean = true,
packageName: String? = null,
showOverLockscreen: Boolean = false,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
if (controller == null || !animate) {
Log.d(TAG, "Starting intent with no animation")
Log.i(TAG, "Starting intent with no animation")
intentStarter(null)
controller?.callOnIntentStartedOnMainThread(willAnimate = false)
return
}
Log.d(TAG, "Starting intent with a launch animation")
val runner = Runner(controller)
val isOnKeyguard = callback.isOnKeyguard()
val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
// Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
val animationAdapter = if (!isOnKeyguard) {
// Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
// keyguard with the animation
val animationAdapter = if (!hideKeyguardWithAnimation) {
RemoteAnimationAdapter(
runner,
ANIMATION_DURATION,
@@ -149,9 +152,11 @@ class ActivityLaunchAnimator(
val willAnimate =
launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS ||
(launchResult == ActivityManager.START_DELIVERED_TO_TOP && isOnKeyguard)
(launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
hideKeyguardWithAnimation)
Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate isOnKeyguard=$isOnKeyguard")
Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " +
"hideKeyguardWithAnimation=$hideKeyguardWithAnimation")
controller.callOnIntentStartedOnMainThread(willAnimate)
// If we expect an animation, post a timeout to cancel it in case the remote animation is
@@ -160,7 +165,7 @@ class ActivityLaunchAnimator(
runner.postTimeout()
// Hide the keyguard using the launch animation instead of the default unlock animation.
if (isOnKeyguard) {
if (hideKeyguardWithAnimation) {
callback.hideKeyguardWithAnimation(runner)
}
}
@@ -424,13 +429,16 @@ class ActivityLaunchAnimator(
nonApps: Array<out RemoteAnimationTarget>?,
iCallback: IRemoteAnimationFinishedCallback?
) {
Log.d(TAG, "Remote animation started")
if (DEBUG) {
Log.d(TAG, "Remote animation started")
}
val window = apps?.firstOrNull {
it.mode == RemoteAnimationTarget.MODE_OPENING
}
if (window == null) {
Log.d(TAG, "Aborting the animation as no window is opening")
Log.i(TAG, "Aborting the animation as no window is opening")
removeTimeout()
iCallback?.invoke()
controller.onLaunchAnimationCancelled()
@@ -500,7 +508,10 @@ class ActivityLaunchAnimator(
val launchContainerOverlay = launchContainer.overlay
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
Log.d(TAG, "Animation started")
if (DEBUG) {
Log.d(TAG, "Animation started")
}
callback.setBlursDisabledForAppLaunch(true)
controller.onLaunchAnimationStart(isExpandingFullyAbove)
@@ -511,7 +522,10 @@ class ActivityLaunchAnimator(
}
override fun onAnimationEnd(animation: Animator?) {
Log.d(TAG, "Animation ended")
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
callback.setBlursDisabledForAppLaunch(false)
iCallback?.invoke()
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
@@ -686,7 +700,7 @@ class ActivityLaunchAnimator(
return
}
Log.d(TAG, "Remote animation timed out")
Log.i(TAG, "Remote animation timed out")
timedOut = true
controller.onLaunchAnimationCancelled()
}
@@ -696,7 +710,7 @@ class ActivityLaunchAnimator(
return
}
Log.d(TAG, "Remote animation was cancelled")
Log.i(TAG, "Remote animation was cancelled")
cancelled = true
removeTimeout()
context.mainExecutor.execute {

View File

@@ -60,8 +60,16 @@ public interface ActivityStarter {
*/
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
void startActivity(Intent intent, boolean dismissShade);
default void startActivity(Intent intent, boolean dismissShade,
@Nullable ActivityLaunchAnimator.Controller animationController) {
startActivity(intent, dismissShade, animationController,
false /* showOverLockscreenWhenLocked */);
}
void startActivity(Intent intent, boolean dismissShade,
@Nullable ActivityLaunchAnimator.Controller animationController);
@Nullable ActivityLaunchAnimator.Controller animationController,
boolean showOverLockscreenWhenLocked);
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
void startActivity(Intent intent, boolean dismissShade, Callback callback);
void postStartActivityDismissingKeyguard(Intent intent, int delay);

View File

@@ -22,11 +22,12 @@
android:color="?android:attr/textColorPrimary">
<item>
<shape
android:shape="oval">
android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface"/>
<size
android:width="@dimen/keyguard_affordance_width"
android:height="@dimen/keyguard_affordance_height"/>
<corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
</shape>
</item>
</ripple>

View File

@@ -918,6 +918,7 @@
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
<dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
<dimen name="keyguard_affordance_vertical_offset">32dp</dimen>

View File

@@ -92,9 +92,11 @@ public class ActivityStarterDelegate implements ActivityStarter {
@Override
public void startActivity(Intent intent, boolean dismissShade,
@Nullable ActivityLaunchAnimator.Controller animationController) {
@Nullable ActivityLaunchAnimator.Controller animationController,
boolean showOverLockscreenWhenLocked) {
mActualStarterOptionalLazy.get().ifPresent(
starter -> starter.startActivity(intent, dismissShade, animationController));
starter -> starter.startActivity(intent, dismissShade, animationController,
showOverLockscreenWhenLocked));
}
@Override

View File

@@ -131,22 +131,16 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
Intent intent = new Intent(mContext, WalletActivity.class)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
if (mKeyguardStateController.isUnlocked()) {
mActivityStarter.startActivity(intent, true /* dismissShade */,
animationController);
} else {
mHost.collapsePanels();
// Do not use ActivityStarter here because the WalletActivity is required to be
// started without prompting keyguard when the device is locked.
mContext.startActivity(intent);
}
mActivityStarter.startActivity(intent, true /* dismissShade */,
animationController, true /* showOverLockscreenWhenLocked */);
} else {
if (mController.getWalletClient().createWalletIntent() == null) {
Intent intent = mController.getWalletClient().createWalletIntent();
if (intent == null) {
Log.w(TAG, "Could not get intent of the wallet app.");
return;
}
mActivityStarter.postStartActivityDismissingKeyguard(
mController.getWalletClient().createWalletIntent(),
intent,
/* delay= */ 0,
animationController);
}

View File

@@ -80,6 +80,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.camera.CameraIntents;
@@ -1044,11 +1045,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
return;
}
ActivityLaunchAnimator.Controller animationController = createLaunchAnimationController(v);
if (mHasCard) {
Intent intent = new Intent(mContext, WalletActivity.class)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
mActivityStarter.startActivity(intent, true /* dismissShade */, animationController,
true /* showOverLockscreenWhenLocked */);
} else {
if (mQuickAccessWalletController.getWalletClient().createWalletIntent() == null) {
Log.w(TAG, "Could not get intent of the wallet app.");
@@ -1056,10 +1059,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
mActivityStarter.postStartActivityDismissingKeyguard(
mQuickAccessWalletController.getWalletClient().createWalletIntent(),
/* delay= */ 0);
/* delay= */ 0, animationController);
}
}
protected ActivityLaunchAnimator.Controller createLaunchAnimationController(View view) {
return ActivityLaunchAnimator.Controller.fromView(view, null);
}
private void onControlsClick(View v) {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;

View File

@@ -672,6 +672,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mNotificationsTint = mState.getNotifTint();
mBehindTint = behindTint;
}
// At the end of a launch animation over the lockscreen, the state is either KEYGUARD or
// SHADE_LOCKED and this code is called. We have to set the notification alpha to 0
// otherwise there is a flicker to its previous value.
if (mKeyguardOccluded) {
mNotificationsAlpha = 0;
}
}
assertAlphasValid();

View File

@@ -51,6 +51,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.TaskInfo;
import android.app.TaskStackBuilder;
import android.app.UiModeManager;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
@@ -644,6 +645,7 @@ public class StatusBar extends SystemUI implements
private boolean mIsOccluded;
private boolean mWereIconsJustHidden;
private boolean mBouncerWasShowingWhenHidden;
private boolean mIsLaunchingActivityOverLockscreen;
private final UserSwitcherController mUserSwitcherController;
private final NetworkController mNetworkController;
@@ -1728,10 +1730,77 @@ public class StatusBar extends SystemUI implements
@Override
public void startActivity(Intent intent, boolean dismissShade,
ActivityLaunchAnimator.Controller animationController) {
startActivityDismissingKeyguard(intent, false, dismissShade,
@Nullable ActivityLaunchAnimator.Controller animationController,
boolean showOverLockscreenWhenLocked) {
// Make sure that we dismiss the keyguard if it is directly dismissable or when we don't
// want to show the activity above it.
if (mKeyguardStateController.isUnlocked() || !showOverLockscreenWhenLocked) {
startActivityDismissingKeyguard(intent, false, dismissShade,
false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */,
0 /* flags */, animationController);
return;
}
boolean animate =
animationController != null && shouldAnimateLaunch(true /* isActivityIntent */,
showOverLockscreenWhenLocked);
ActivityLaunchAnimator.Controller controller = null;
if (animate) {
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
ActivityLaunchAnimator.Controller delegate = wrapAnimationController(
animationController, dismissShade);
controller = new DelegateLaunchAnimatorController(delegate) {
@Override
public void onIntentStarted(boolean willAnimate) {
getDelegate().onIntentStarted(willAnimate);
if (willAnimate) {
StatusBar.this.mIsLaunchingActivityOverLockscreen = true;
}
}
@Override
public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
// Set mIsLaunchingActivityOverLockscreen to false before actually finishing the
// animation so that we can assume that mIsLaunchingActivityOverLockscreen
// being true means that we will collapse the shade (or at least run the
// post collapse runnables) later on.
StatusBar.this.mIsLaunchingActivityOverLockscreen = false;
getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
}
@Override
public void onLaunchAnimationCancelled() {
// Set mIsLaunchingActivityOverLockscreen to false before actually finishing the
// animation so that we can assume that mIsLaunchingActivityOverLockscreen
// being true means that we will collapse the shade (or at least run the
// post collapse runnables) later on.
StatusBar.this.mIsLaunchingActivityOverLockscreen = false;
getDelegate().onLaunchAnimationCancelled();
}
};
} else if (dismissShade) {
// The animation will take care of dismissing the shade at the end of the animation. If
// we don't animate, collapse it directly.
collapseShade();
}
mActivityLaunchAnimator.startIntentWithAnimation(controller, animate,
intent.getPackage(), showOverLockscreenWhenLocked, (adapter) -> TaskStackBuilder
.create(mContext)
.addNextIntent(intent)
.startActivities(getActivityOptions(getDisplayId(), adapter),
UserHandle.CURRENT));
}
/**
* Whether we are currently animating an activity launch above the lockscreen (occluding
* activity).
*/
public boolean isLaunchingActivityOverLockscreen() {
return mIsLaunchingActivityOverLockscreen;
}
@Override
@@ -1892,23 +1961,30 @@ public class StatusBar extends SystemUI implements
*
* Note: This method must be called *before* dismissing the keyguard.
*/
public boolean shouldAnimateLaunch(boolean isActivityIntent) {
public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) {
// TODO(b/184121838): Support launch animations when occluded.
if (isOccluded()) {
return false;
}
// Always animate if we are unlocked.
if (!mKeyguardStateController.isShowing()) {
// Always animate if we are not showing the keyguard or if we animate over the lockscreen
// (without unlocking it).
if (showOverLockscreen || !mKeyguardStateController.isShowing()) {
return true;
}
// If we are locked, only animate if remote unlock animations are enabled. We also don't
// animate non-activity launches as they can break the animation.
// If we are locked and have to dismiss the keyguard, only animate if remote unlock
// animations are enabled. We also don't animate non-activity launches as they can break the
// animation.
// TODO(b/184121838): Support non activity launches on the lockscreen.
return isActivityIntent && KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation;
}
/** Whether we should animate an activity launch. */
public boolean shouldAnimateLaunch(boolean isActivityIntent) {
return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */);
}
public boolean isDeviceInVrMode() {
return mPresenter.isDeviceInVrMode();
}

View File

@@ -73,6 +73,8 @@ import java.util.Optional;
import javax.inject.Inject;
import dagger.Lazy;
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -111,6 +113,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
private final Lazy<ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@Override
public void onFullyShown() {
@@ -243,7 +246,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
KeyguardBouncer.Factory keyguardBouncerFactory,
WakefulnessLifecycle wakefulnessLifecycle,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory) {
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Lazy<ShadeController> shadeController) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -260,6 +264,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mWakefulnessLifecycle = wakefulnessLifecycle;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
}
@Override
@@ -634,6 +639,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
});
return;
}
if (mStatusBar.isLaunchingActivityOverLockscreen()) {
mOccluded = true;
// When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
// collapse runnables will be run.
mShadeController.get().addPostCollapseAction(() -> {
mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
reset(true /* hideBouncerWhenShowing */);
});
return;
}
} else if (!occluded && mOccluded && mShowing) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);

View File

@@ -228,7 +228,9 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTile.handleClick(null /* view */);
mTestableLooper.processAllMessages();
verify(mSpiedContext).startActivity(mIntentCaptor.capture());
verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true) /* dismissShade */,
(ActivityLaunchAnimator.Controller) eq(null),
eq(true) /* showOverLockscreenWhenLocked */);
Intent nextStartedIntent = mIntentCaptor.getValue();
String walletClassName = "com.android.systemui.wallet.ui.WalletActivity";
@@ -246,7 +248,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true) /* dismissShade */,
(ActivityLaunchAnimator.Controller) eq(null));
(ActivityLaunchAnimator.Controller) eq(null),
eq(true) /* showOverLockscreenWhenLocked */);
Intent nextStartedIntent = mIntentCaptor.getValue();
String walletClassName = "com.android.systemui.wallet.ui.WalletActivity";

View File

@@ -64,6 +64,8 @@ import org.mockito.MockitoAnnotations;
import java.util.Optional;
import dagger.Lazy;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -103,6 +105,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private Lazy<ShadeController> mShadeController;
private WakefulnessLifecycle mWakefulnessLifecycle;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -133,7 +137,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mKeyguardBouncerFactory,
mWakefulnessLifecycle,
mUnlockedScreenOffAnimationController,
mKeyguardMessageAreaFactory);
mKeyguardMessageAreaFactory,
mShadeController);
mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
mNotificationPanelView, mBiometrucUnlockController,
mNotificationContainer, mBypassController);