/**
* This interface abstracts a collection of Keyframe objects and is called by
* ValueAnimator to calculate values between those keyframes for a given animation.
+ * @hide
*/
-interface Keyframes extends Cloneable {
+public interface Keyframes extends Cloneable {
/**
* Sets the TypeEvaluator to be used when calculating animated values. This object
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mSystemBroadcastReceiver, filter);
+
+ getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
}
@Override
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.ITaskStackListener;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
+import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
-import android.view.Surface;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.KeyboardShortcutsReceiver;
import android.view.WindowManagerGlobal;
}
/** Docks a task to the side of the screen and starts it. */
- public void startTaskInDockedMode(Context context, View view, int taskId, int createMode,
- Bitmap headerBitmap, Handler handler, OnAnimationStartedListener startedListener) {
+ public void startTaskInDockedMode(int taskId, int createMode) {
if (mIam == null) return;
try {
// TODO: Determine what animation we want for the incoming task
- final ActivityOptions options = ActivityOptions.makeThumbnailAspectScaleUpAnimation(
- view, headerBitmap, 0, 0, (int) (view.getWidth() * view.getScaleX()),
- (int) (view.getHeight() * view.getScaleY()), handler, startedListener);
+ final ActivityOptions options = ActivityOptions.makeBasic();
options.setDockCreateMode(createMode);
options.setLaunchStackId(DOCKED_STACK_ID);
mIam.startActivityFromRecents(taskId, options.toBundle());
}
}
+ public void overridePendingAppTransitionMultiThumbFuture(
+ IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
+ boolean scaleUp) {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
+ scaleUp);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to override transition: " + e);
+ }
+ }
+
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_ACTIVITY_PINNED = 2;
import android.annotation.Nullable;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
-import android.util.Log;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
*/
public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
final TaskStackView stackView, final TaskView taskView,
- final boolean screenPinningRequested, final Rect bounds, int destinationStack) {
+ final boolean screenPinningRequested, final Rect bounds, final int destinationStack) {
final ActivityOptions opts = ActivityOptions.makeBasic();
if (bounds != null) {
opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
final ActivityOptions.OnAnimationStartedListener animStartedListener;
final IAppTransitionAnimationSpecsFuture transitionFuture;
if (taskView != null) {
- transitionFuture = getAppTransitionFuture(task, stackView, destinationStack);
+ transitionFuture = getAppTransitionFuture(new AnimationSpecComposer() {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ return composeAnimationSpecs(task, stackView, destinationStack);
+ }
+ });
animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
@Override
public void onAnimationStarted() {
}
}
+ public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
+ if (listener == null) {
+ return null;
+ }
+ return new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onAnimationStarted();
+ }
+ });
+ }
+ };
+ }
+
/**
* Starts the activity for the launch task.
*
}
if (transitionFuture != null) {
- IRemoteCallback.Stub callback = null;
- if (animStartedListener != null) {
- callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (animStartedListener != null) {
- animStartedListener.onAnimationStarted();
- }
- }
- });
- }
- };
- }
- try {
- synchronized (this) {
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- }
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
- callback, true /* scaleUp */);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override transition: " + e);
- }
+ ssp.overridePendingAppTransitionMultiThumbFuture(transitionFuture,
+ wrapStartedListener(animStartedListener), true /* scaleUp */);
}
}
/**
* Creates a future which will later be queried for animation specs for this current transition.
+ *
+ * @param composer The implementation that composes the specs on the UI thread.
*/
- private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final Task task,
- final TaskStackView stackView, final int destinationStack) {
+ public IAppTransitionAnimationSpecsFuture getAppTransitionFuture(
+ final AnimationSpecComposer composer) {
+ synchronized (this) {
+ mAppTransitionAnimationSpecs = SPECS_WAITING;
+ }
return new IAppTransitionAnimationSpecsFuture.Stub() {
@Override
public AppTransitionAnimationSpec[] get() throws RemoteException {
@Override
public void run() {
synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composeAnimationSpecs(task, stackView,
- destinationStack);
+ mAppTransitionAnimationSpecs = composer.composeSpecs();
RecentsTransitionHelper.this.notifyAll();
}
}
}
/**
+ * Composes the transition spec when docking a task, which includes a full task bitmap.
+ */
+ public List<AppTransitionAnimationSpec> composeDockAnimationSpec(
+ TaskView taskView, Rect transform) {
+ TaskViewTransform viewTransform = new TaskViewTransform();
+ viewTransform.fillIn(taskView);
+ return Collections.singletonList(new AppTransitionAnimationSpec(taskView.getTask().key.id,
+ RecentsTransitionHelper.composeTaskBitmap(taskView, viewTransform), transform));
+ }
+
+ /**
* Composes the animation specs for all the tasks in the target stack.
*/
private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
}
return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
}
+
+ public interface AnimationSpecComposer {
+ List<AppTransitionAnimationSpec> composeSpecs();
+ }
}
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
+import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.Interpolators;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
taskViewRect.right, taskViewRect.bottom);
- // Remove the task view after it is docked
- mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
- stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
- null);
- tmpTransform.alpha = 0;
- tmpTransform.scale = TaskStackView.DRAG_SCALE_FACTOR;
- tmpTransform.rect.set(taskViewRect);
final OnAnimationStartedListener startedListener = new OnAnimationStartedListener() {
@Override
public void onAnimationStarted() {
true /* fromDockGesture */);
}
};
- mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
- new AnimationProps(125, Interpolators.ALPHA_OUT,
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Dock the task and launch it
- SystemServicesProxy ssp = Recents.getSystemServices();
- TaskViewTransform transform = new TaskViewTransform();
- transform.fillIn(event.taskView);
- ssp.startTaskInDockedMode(getContext(), event.taskView,
- event.task.key.id, dockState.createMode,
- RecentsTransitionHelper.composeTaskBitmap(
- event.taskView, transform),
- mHandler, startedListener);
- }
- }));
+
+ // Dock the task and launch it
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
+ final Rect taskRect = getTaskRect(event.taskView);
+ IAppTransitionAnimationSpecsFuture future = mTransitionHelper.getAppTransitionFuture(
+ new AnimationSpecComposer() {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ return mTransitionHelper.composeDockAnimationSpec(
+ event.taskView, taskRect);
+ }
+ });
+ ssp.overridePendingAppTransitionMultiThumbFuture(future,
+ mTransitionHelper.wrapStartedListener(startedListener),
+ true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP);
} else {
}
}
+ private Rect getTaskRect(TaskView taskView) {
+ int[] location = taskView.getLocationOnScreen();
+ int viewX = location[0];
+ int viewY = location[1];
+ return new Rect(viewX, viewY,
+ (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
+ (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
+ }
+
public final void onBusEvent(DraggingInRecentsEvent event) {
if (mTaskStackView.getTaskViews().size() > 0) {
setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
// Get the focused task before launching launcher.
if (mUserController.isLockScreenDisabled(currentUserId)) {
+
// If there is no device lock, we will show the profile's credential page.
// startActivityFromRecentsInner is intercepted and will forward user to it.
if (mFocusedActivity != null) {
- startActivityFromRecentsInner(mFocusedActivity.task.taskId, null);
+ mStackSupervisor.startActivityFromRecentsInner(
+ mFocusedActivity.task.taskId, null);
}
} else {
// Showing launcher to avoid user entering credential twice.
+ " in=" + stacks);
}
+ /**
+ * Puts a task into resizing mode during the next app transition.
+ *
+ * @param taskId the id of the task to put into resizing mode
+ */
+ private void setResizingDuringAnimation(int taskId) {
+ mResizingTasksDuringAnimation.add(taskId);
+ mWindowManager.setTaskDockedResizing(taskId, true);
+ }
+
final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
final TaskRecord task;
final int callingUid;
// the window renders full-screen with the background filling the void. Also only
// call this at the end to make sure that tasks exists on the window manager side.
if (launchStackId == DOCKED_STACK_ID) {
- mResizingTasksDuringAnimation.add(task.taskId);
- mWindowManager.setTaskDockedResizing(task.taskId, true);
+ setResizingDuringAnimation(taskId);
}
return ActivityManager.START_TASK_TO_FRONT;
}
intent = task.intent;
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
userId = task.userId;
- return mService.startActivityInPackage(callingUid, callingPackage, intent, null, null, null,
- 0, 0, bOptions, userId, null, task);
+ int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
+ null, null, 0, 0, bOptions, userId, null, task);
+ if (launchStackId == DOCKED_STACK_ID) {
+ setResizingDuringAnimation(task.taskId);
+ }
+ return result;
}
}
}
if (startedActivityStackId == DOCKED_STACK_ID && prevFocusedStackId == HOME_STACK_ID) {
- // We launch an activity while being in home stack, which means either launcher or
- // recents into docked stack. We don't want the launched activity to be alone in a
- // docked stack, so we want to immediately launch recents too.
- if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
- mWindowManager.showRecentApps();
- return;
+ final ActivityStack homeStack = mSupervisor.getStack(HOME_STACK_ID);
+ final ActivityRecord topActivityHomeStack = homeStack != null
+ ? homeStack.topRunningActivityLocked() : null;
+ if (topActivityHomeStack == null
+ || topActivityHomeStack.mActivityType != RECENTS_ACTIVITY_TYPE) {
+ // We launch an activity while being in home stack, which means either launcher or
+ // recents into docked stack. We don't want the launched activity to be alone in a
+ // docked stack, so we want to immediately launch recents too.
+ if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
+ mWindowManager.showRecentApps();
+ return;
+ }
}
if (startedActivityStackId == PINNED_STACK_ID
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
-import com.android.internal.R;
import com.android.internal.util.DumpUtils.Dump;
import com.android.server.AttributeCache;
import com.android.server.wm.WindowManagerService.H;
static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+ private static final Interpolator THUMBNAIL_DOCK_INTERPOLATOR =
+ new PathInterpolator(0.85f, 0f, 1f, 1f);
+
/**
* Maximum duration for the clip reveal animation. This is used when there is a lot of movement
* involved, to make it more understandable.
a.setDuration(duration);
}
a.setFillAfter(true);
- a.setInterpolator(interpolator);
+ if (interpolator != null) {
+ a.setInterpolator(interpolator);
+ }
a.initialize(appWidth, appHeight, appWidth, appHeight);
return a;
}
scale.setInterpolator(interpolator);
scale.setDuration(duration);
Animation alpha = new AlphaAnimation(1f, 0f);
- alpha.setInterpolator(mThumbnailFadeOutInterpolator);
- alpha.setDuration(duration);
+ alpha.setInterpolator(mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+ ? THUMBNAIL_DOCK_INTERPOLATOR : mThumbnailFadeOutInterpolator);
+ alpha.setDuration(mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+ ? duration / 2
+ : duration);
Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
translate.setInterpolator(interpolator);
translate.setDuration(duration);
// Containing frame is in screen space, but we need the clip rect in the
// app space.
mTmpToClipRect.offsetTo(0, 0);
- mTmpToClipRect.right = (int) (mTmpToClipRect.right * scaleW);
- mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom * scaleW);
+ mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
+ mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
if (contentInsets != null) {
mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
}
return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
- TOUCH_RESPONSE_INTERPOLATOR);
+ null);
}
private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
// Almost no x-change - use linear animation
- if (Math.abs(toX - fromX) < 1f) {
+ if (Math.abs(toX - fromX) < 1f || mNextAppTransition != TRANSIT_DOCK_TASK_FROM_RECENTS) {
return new TranslateAnimation(fromX, toX, fromY, toY);
} else {
final Path path = createCurvedPath(fromX, toX, fromY, toY);
private long getAspectScaleDuration() {
if (mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS) {
- return (long) (THUMBNAIL_APP_TRANSITION_DURATION * 1f);
+ return (long) (THUMBNAIL_APP_TRANSITION_DURATION * 1.35f);
} else {
return THUMBNAIL_APP_TRANSITION_DURATION;
}
final int thumbStartX = mTmpRect.left - containingFrame.left;
final int thumbStartY = mTmpRect.top - containingFrame.top;
- // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
- float scale = 1f;
- int scaledTopDecor = 0;
-
switch (thumbTransitState) {
case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
// We scale the width and clip to the top/left square
+ // We scale the width and clip to the top/left square
+ float scale = thumbWidth /
+ (appWidth - contentInsets.left - contentInsets.right);
+ int unscaledThumbHeight = (int) (thumbHeight / scale);
+ mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
+
+ mNextAppTransitionInsets.set(contentInsets);
+
Animation scaleAnim = new ScaleAnimation(
scaleUp ? scale : 1, scaleUp ? 1 : scale,
scaleUp ? scale : 1, scaleUp ? 1 : scale,
- containingFrame.width() / 2,
- containingFrame.height() / 2 + contentInsets.top);
+ containingFrame.width() / 2f,
+ containingFrame.height() / 2f + contentInsets.top);
final float targetX = (mTmpRect.left - containingFrame.left);
- final float x = containingFrame.width() / 2
- - containingFrame.width() / 2 * scale;
+ final float x = containingFrame.width() / 2f
+ - containingFrame.width() / 2f * scale;
final float targetY = (mTmpRect.top - containingFrame.top);
- final float y = containingFrame.height() / 2
- - containingFrame.height() / 2 * scale;
+ final float y = containingFrame.height() / 2f
+ - containingFrame.height() / 2f * scale;
final float startX = targetX - x;
final float startY = targetY - y;
Animation clipAnim = scaleUp
? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
: new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
Animation translateAnim = scaleUp
- ? createCurvedMotion(startX, 0, startY - scaledTopDecor, 0)
- : createCurvedMotion(0, startX, 0, startY - scaledTopDecor);
+ ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
+ : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
+
set.addAnimation(clipAnim);
set.addAnimation(scaleAnim);
set.addAnimation(translateAnim);
set.addAnimation(clipAnim);
set.addAnimation(translateAnim);
}
- set.setZAdjustment(Animation.ZORDER_TOP);
a = set;
a.setZAdjustment(Animation.ZORDER_TOP);
}
int getAppStackClipMode() {
return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH
+ || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
? STACK_CLIP_NONE
: STACK_CLIP_AFTER_ANIM;
}
w.mLayer = layer;
w.mWinAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() +
getSpecialWindowAnimLayerAdjustment(w);
+ if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0
+ && w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
+ w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
+ }
}
void dump(PrintWriter pw, String s) {