Merge "Make AppTransition animations sharable (2/n)" into sc-dev

This commit is contained in:
Tony Huang
2021-02-23 09:26:05 +00:00
committed by Android (Google) Code Review
5 changed files with 210 additions and 46 deletions

View File

@@ -180,6 +180,7 @@ public class TransitionAnimation {
"android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
}
/** Load animation by resource Id from specific LayoutParams. */
@Nullable
private Animation loadAnimationRes(LayoutParams lp, int resId) {
Context context = mContext;
@@ -193,6 +194,7 @@ public class TransitionAnimation {
return null;
}
/** Load animation by resource Id from specific package. */
@Nullable
private Animation loadAnimationRes(String packageName, int resId) {
if (ResourceId.isValid(resId)) {
@@ -204,6 +206,13 @@ public class TransitionAnimation {
return null;
}
/** Load animation by resource Id from android package. */
@Nullable
public Animation loadDefaultAnimationRes(int resId) {
return loadAnimationRes("android", resId);
}
/** Load animation by attribute Id from specific LayoutParams */
@Nullable
public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
int resId = Resources.ID_NULL;
@@ -222,6 +231,25 @@ public class TransitionAnimation {
return null;
}
/** Load animation by attribute Id from android package. */
@Nullable
public Animation loadDefaultAnimationAttr(int animAttr) {
int resId = Resources.ID_NULL;
Context context = mContext;
if (animAttr >= 0) {
AttributeCache.Entry ent = getCachedAnimations("android",
mDefaultWindowAnimationStyleResId);
if (ent != null) {
context = ent.context;
resId = ent.array.getResourceId(animAttr, 0);
}
}
if (ResourceId.isValid(resId)) {
return loadAnimationSafely(context, resId, mTag);
}
return null;
}
@Nullable
private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
if (mDebug) {

View File

@@ -16,55 +16,78 @@
package com.android.wm.shell.transition;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static final int MAX_ANIMATION_DURATION = 3000;
private final TransactionPool mTransactionPool;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
/** Keeps track of the currently-running animations associated with each transition. */
private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
DefaultTransitionHandler(@NonNull TransactionPool transactionPool,
private float mTransitionAnimationScaleSetting = 1.0f;
DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mTransactionPool = transactionPool;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
AttributeCache.init(context);
}
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"start default transition animation, info = %s", info);
if (mAnimations.containsKey(transition)) {
throw new IllegalStateException("Got a duplicate startAnimation call for "
+ transition);
}
final ArrayList<Animator> animations = new ArrayList<>();
mAnimations.put(transition, animations);
final boolean isOpening = Transitions.isOpeningType(info.getType());
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
@@ -77,19 +100,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// Don't animate anything with an animating parent
if (change.getParent() != null) continue;
final int mode = change.getMode();
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
// This received a transferred starting window, so don't animate
continue;
}
// fade in
startExampleAnimation(
animations, change.getLeash(), true /* show */, onAnimFinish);
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
// fade out
startExampleAnimation(
animations, change.getLeash(), false /* show */, onAnimFinish);
Animation a = loadAnimation(info.getType(), change);
if (a != null) {
startAnimInternal(animations, a, change.getLeash(), onAnimFinish);
}
}
t.apply();
@@ -105,32 +118,93 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return null;
}
// TODO(shell-transitions): real animations
private void startExampleAnimation(@NonNull ArrayList<Animator> animations,
@NonNull SurfaceControl leash, boolean show, @NonNull Runnable finishCallback) {
final float end = show ? 1.f : 0.f;
final float start = 1.f - end;
@Override
public void setAnimScaleSetting(float scale) {
mTransitionAnimationScaleSetting = scale;
}
@Nullable
private Animation loadAnimation(int type, TransitionInfo.Change change) {
// TODO(b/178678389): It should handle more type animation here
Animation a = null;
final boolean isOpening = Transitions.isOpeningType(type);
final int mode = change.getMode();
final int flags = change.getFlags();
if (mode == TRANSIT_OPEN && isOpening) {
if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
// This received a transferred starting window, so don't animate
return null;
}
if (change.getTaskInfo() != null) {
a = mTransitionAnimation.loadDefaultAnimationAttr(
R.styleable.WindowAnimation_taskOpenEnterAnimation);
} else {
a = mTransitionAnimation.loadDefaultAnimationRes((flags & FLAG_TRANSLUCENT) == 0
? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter);
}
} else if (mode == TRANSIT_TO_FRONT && isOpening) {
if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
// This received a transferred starting window, so don't animate
return null;
}
a = mTransitionAnimation.loadDefaultAnimationAttr(
R.styleable.WindowAnimation_taskToFrontEnterAnimation);
} else if (mode == TRANSIT_CLOSE && !isOpening) {
if (change.getTaskInfo() != null) {
a = mTransitionAnimation.loadDefaultAnimationAttr(
R.styleable.WindowAnimation_taskCloseExitAnimation);
} else {
a = mTransitionAnimation.loadDefaultAnimationRes((flags & FLAG_TRANSLUCENT) == 0
? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit);
}
} else if (mode == TRANSIT_TO_BACK && !isOpening) {
a = mTransitionAnimation.loadDefaultAnimationAttr(
R.styleable.WindowAnimation_taskToBackExitAnimation);
} else if (mode == TRANSIT_CHANGE) {
// In the absence of a specific adapter, we just want to keep everything stationary.
a = new AlphaAnimation(1.f, 1.f);
a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
}
if (a != null) {
Rect start = change.getStartAbsBounds();
Rect end = change.getEndAbsBounds();
a.restrictDuration(MAX_ANIMATION_DURATION);
a.initialize(end.width(), end.height(), start.width(), start.height());
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
}
return a;
}
private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
@NonNull SurfaceControl leash, @NonNull Runnable finishCallback) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
va.setDuration(500);
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final Transformation transformation = new Transformation();
final float[] matrix = new float[9];
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
transaction.apply();
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix);
});
final Runnable finisher = () -> {
transaction.setAlpha(leash, end);
transaction.apply();
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix);
mTransactionPool.release(transaction);
mMainExecutor.execute(() -> {
animations.remove(va);
finishCallback.run();
});
};
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
@@ -140,11 +214,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
public void onAnimationCancel(Animator animation) {
finisher.run();
}
@Override
public void onAnimationRepeat(Animator animation) { }
});
animations.add(va);
mAnimExecutor.execute(va::start);
}
private static void applyTransformation(long time, SurfaceControl.Transaction t,
SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) {
anim.getTransformation(time, transformation);
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
t.apply();
}
}

View File

@@ -25,9 +25,13 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
@@ -43,6 +47,7 @@ import android.window.WindowOrganizer;
import androidx.annotation.BinderThread;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -63,6 +68,7 @@ public class Transitions {
SystemProperties.getBoolean("persist.debug.shell_transit", false);
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
@@ -72,6 +78,8 @@ public class Transitions {
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
private float mTransitionAnimationScaleSetting = 1.0f;
private static final class ActiveTransition {
TransitionHandler mFirstHandler = null;
}
@@ -84,26 +92,46 @@ public class Transitions {
}
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
@NonNull Context context, @NonNull ShellExecutor mainExecutor,
@NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
mHandlers.add(new DefaultTransitionHandler(pool, mainExecutor, animExecutor));
mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
ContentResolver resolver = context.getContentResolver();
mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
Settings.Global.TRANSITION_ANIMATION_SCALE,
context.getResources().getFloat(
R.dimen.config_appTransitionAnimationDurationScaleDefault));
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver());
}
private Transitions() {
mOrganizer = null;
mContext = null;
mMainExecutor = null;
mAnimExecutor = null;
mPlayerImpl = null;
mRemoteTransitionHandler = null;
}
private void dispatchAnimScaleSetting(float scale) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
mHandlers.get(i).setAnimScaleSetting(scale);
}
}
/** Create an empty/non-registering transitions object for system-ui tests. */
@VisibleForTesting
public static RemoteTransitions createEmptyForTesting() {
@@ -368,6 +396,13 @@ public class Transitions {
@Nullable
WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request);
/**
* Sets transition animation scale settings value to handler.
*
* @param scale The setting value of transition animation scale.
*/
default void setAnimScaleSetting(float scale) {}
}
@BinderThread
@@ -404,4 +439,21 @@ public class Transitions {
});
}
}
private class SettingsObserver extends ContentObserver {
SettingsObserver() {
super(null);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
mTransitionAnimationScaleSetting = Settings.Global.getFloat(
mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
mTransitionAnimationScaleSetting);
mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
}
}
}

View File

@@ -41,6 +41,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -58,6 +59,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
@@ -78,6 +80,8 @@ public class ShellTransitionTests {
private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
private final TransactionPool mTransactionPool = mock(TransactionPool.class);
private final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
@@ -90,8 +94,8 @@ public class ShellTransitionTests {
@Test
public void testBasicTransitionFlow() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
mAnimExecutor);
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken = new Binder();
@@ -109,8 +113,8 @@ public class ShellTransitionTests {
@Test
public void testNonDefaultHandler() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
mAnimExecutor);
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
@@ -188,8 +192,8 @@ public class ShellTransitionTests {
@Test
public void testRequestRemoteTransition() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
mAnimExecutor);
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -255,8 +259,8 @@ public class ShellTransitionTests {
@Test
public void testRegisteredRemoteTransition() {
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
mAnimExecutor);
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
mMainExecutor, mAnimExecutor);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};

View File

@@ -380,9 +380,9 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
@ShellMainThread ShellExecutor mainExecutor,
Context context, @ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor) {
return new Transitions(organizer, pool, mainExecutor, animExecutor);
return new Transitions(organizer, pool, context, mainExecutor, animExecutor);
}
//