blob: a82137e07c1618a615a21ddfb9136b996b52ebdc [file] [log] [blame]
/*
* Copyright (C) 2019 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.quickstep;
import static android.content.Intent.EXTRA_COMPONENT_NAME;
import static android.content.Intent.EXTRA_USER;
import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
import static com.android.launcher3.Utilities.createHomeIntent;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.ParcelUuid;
import android.os.UserHandle;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import androidx.annotation.NonNull;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.UUID;
import java.util.function.Consumer;
/**
* Handles the navigation gestures when a 3rd party launcher is the default home activity.
*/
@TargetApi(Build.VERSION_CODES.R)
public class FallbackSwipeHandler extends
AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> {
/**
* Message used for receiving gesture nav contract information. We use a static messenger to
* avoid leaking too make binders in case the receiving launcher does not handle the contract
* properly.
*/
private static StaticMessageReceiver sMessageReceiver = null;
private FallbackHomeAnimationFactory mActiveAnimationFactory;
private final boolean mRunningOverHome;
private final Matrix mTmpMatrix = new Matrix();
private float mMaxLauncherScale = 1;
public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, InputConsumerController inputConsumer) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
continuingLastGesture, inputConsumer);
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
if (mRunningOverHome) {
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
}
}
@Override
protected void initTransitionEndpoints(DeviceProfile dp) {
super.initTransitionEndpoints(dp);
if (mRunningOverHome) {
// Full screen scale should be independent of remote target handle
mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator()
.getFullScreenScale();
}
}
private void updateHomeActivityTransformDuringSwipeUp(SurfaceParams.Builder builder,
RemoteAnimationTargetCompat app, TransformParams params) {
setHomeScaleAndAlpha(builder, app, mCurrentShift.value,
Utilities.boundToRange(1 - mCurrentShift.value, 0, 1));
}
private void setHomeScaleAndAlpha(SurfaceParams.Builder builder,
RemoteAnimationTargetCompat app, float verticalShift, float alpha) {
float scale = Utilities.mapRange(verticalShift, 1, mMaxLauncherScale);
mTmpMatrix.setScale(scale, scale,
app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
builder.withMatrix(mTmpMatrix).withAlpha(alpha);
}
@Override
protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
RemoteAnimationTargetCompat runningTaskTarget) {
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
Intent intent = new Intent(mGestureState.getHomeIntent());
mActiveAnimationFactory.addGestureContract(intent);
try {
mContext.startActivity(intent, options.toBundle());
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
mContext.startActivity(createHomeIntent());
}
return mActiveAnimationFactory;
}
@Override
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
if (mActiveAnimationFactory != null
&& mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
mActiveAnimationFactory = null;
return false;
}
return super.handleTaskAppeared(appearedTaskTarget);
}
@Override
protected void finishRecentsControllerToHome(Runnable callback) {
mRecentsAnimationController.finish(
false /* toRecents */, callback, true /* sendUserLeaveHint */);
}
@Override
protected void switchToScreenshot() {
if (mRunningOverHome) {
// When the current task is home, then we don't need to capture anything
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
super.switchToScreenshot();
}
}
@Override
protected void notifyGestureAnimationStartToRecents() {
if (mRunningOverHome) {
if (SysUINavigationMode.getMode(mContext).hasGestures) {
mRecentsView.onGestureAnimationStartOnHome(
new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()});
}
} else {
super.notifyGestureAnimationStartToRecents();
}
}
private class FallbackHomeAnimationFactory extends HomeAnimationFactory {
private final Rect mTempRect = new Rect();
private final TransformParams mHomeAlphaParams = new TransformParams();
private final AnimatedFloat mHomeAlpha;
private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
private final RectF mTargetRect = new RectF();
private SurfaceControl mSurfaceControl;
private final long mDuration;
private RectFSpringAnim mSpringAnim;
FallbackHomeAnimationFactory(long duration) {
mDuration = duration;
if (mRunningOverHome) {
mHomeAlpha = new AnimatedFloat();
mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
mVerticalShiftForScale.value = mCurrentShift.value;
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
FallbackHomeAnimationFactory.this
::updateHomeActivityTransformDuringHomeAnim));
} else {
mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
mHomeAlpha.value = 0;
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
FallbackHomeAnimationFactory.this
::updateHomeActivityTransformDuringHomeAnim));
}
mRecentsAlpha.value = 1;
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
FallbackHomeAnimationFactory.this
::updateRecentsActivityTransformDuringHomeAnim));
}
@NonNull
@Override
public RectF getWindowTargetRect() {
if (mTargetRect.isEmpty()) {
mTargetRect.set(super.getWindowTargetRect());
}
return mTargetRect;
}
private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
RemoteAnimationTargetCompat app, TransformParams params) {
builder.withAlpha(mRecentsAlpha.value);
}
private void updateHomeActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
RemoteAnimationTargetCompat app, TransformParams params) {
setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value);
}
@NonNull
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
PendingAnimation pa = new PendingAnimation(mDuration);
pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCEL);
return pa.createPlaybackController();
}
private void updateHomeAlpha() {
if (mHomeAlphaParams.getTargetSet() != null) {
mHomeAlphaParams.applySurfaceParams(
mHomeAlphaParams.createSurfaceParams(BuilderProxy.NO_OP));
}
}
public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
RemoteAnimationTargets targets = new RemoteAnimationTargets(
new RemoteAnimationTargetCompat[] {appearedTaskTarget},
new RemoteAnimationTargetCompat[0], new RemoteAnimationTargetCompat[0],
appearedTaskTarget.mode);
mHomeAlphaParams.setTargetSet(targets);
updateHomeAlpha();
return true;
}
return false;
}
@Override
public void playAtomicAnimation(float velocity) {
ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
alphaAnim.setDuration(mDuration).setInterpolator(ACCEL);
alphaAnim.start();
if (mRunningOverHome) {
// Spring back launcher scale
new SpringAnimationBuilder(mContext)
.setStartValue(mVerticalShiftForScale.value)
.setEndValue(0)
.setStartVelocity(-velocity / mTransitionDragLength)
.setMinimumVisibleChange(1f / mDp.heightPx)
.setDampingRatio(0.6f)
.setStiffness(800)
.build(mVerticalShiftForScale, AnimatedFloat.VALUE)
.start();
}
}
@Override
public void setAnimation(RectFSpringAnim anim) {
mSpringAnim = anim;
}
private void onMessageReceived(Message msg) {
try {
Bundle data = msg.getData();
RectF position = data.getParcelable(EXTRA_ICON_POSITION);
if (!position.isEmpty()) {
mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE);
mTargetRect.set(position);
if (mSpringAnim != null) {
mSpringAnim.onTargetPositionChanged();
}
}
} catch (Exception e) {
// Ignore
}
}
@Override
public void update(RectF currentRect, float progress, float radius) {
if (mSurfaceControl != null) {
currentRect.roundOut(mTempRect);
Transaction t = new Transaction();
try {
t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
t.apply();
} catch (RuntimeException e) {
// Ignore
}
}
}
private void addGestureContract(Intent intent) {
if (mRunningOverHome || mGestureState.getRunningTask() == null) {
return;
}
TaskKey key = new TaskKey(mGestureState.getRunningTask());
if (key.getComponent() != null) {
if (sMessageReceiver == null) {
sMessageReceiver = new StaticMessageReceiver();
}
Bundle gestureNavContract = new Bundle();
gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent());
gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId));
gestureNavContract.putParcelable(EXTRA_REMOTE_CALLBACK,
sMessageReceiver.newCallback(this::onMessageReceived));
intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract);
}
}
}
private static class StaticMessageReceiver implements Handler.Callback {
private final Messenger mMessenger =
new Messenger(new Handler(Looper.getMainLooper(), this));
private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID());
private WeakReference<Consumer<Message>> mCurrentCallback = new WeakReference<>(null);
public Message newCallback(Consumer<Message> callback) {
mCurrentUID = new ParcelUuid(UUID.randomUUID());
mCurrentCallback = new WeakReference<>(callback);
Message msg = Message.obtain();
msg.replyTo = mMessenger;
msg.obj = mCurrentUID;
return msg;
}
@Override
public boolean handleMessage(@NonNull Message message) {
if (mCurrentUID.equals(message.obj)) {
Consumer<Message> consumer = mCurrentCallback.get();
if (consumer != null) {
consumer.accept(message);
return true;
}
}
return false;
}
}
}