Merge changes from topic "remote_animations"

* changes:
  Remote animations (app-controlled animations)
  Defer hiding clients until animation is done
This commit is contained in:
Jorim Jaggi
2018-01-17 15:59:52 +00:00
committed by Android (Google) Code Review
20 changed files with 891 additions and 42 deletions

View File

@@ -324,6 +324,8 @@ java_library {
"core/java/android/view/IOnKeyguardExitResult.aidl",
"core/java/android/view/IPinnedStackController.aidl",
"core/java/android/view/IPinnedStackListener.aidl",
"core/java/android/view/IRemoteAnimationRunner.aidl",
"core/java/android/view/IRemoteAnimationFinishedCallback.aidl",
"core/java/android/view/IRotationWatcher.aidl",
"core/java/android/view/IWallpaperVisibilityListener.aidl",
"core/java/android/view/IWindow.aidl",

View File

@@ -16,12 +16,14 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
@@ -44,6 +46,7 @@ import android.util.Pair;
import android.util.Slog;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -241,6 +244,8 @@ public class ActivityOptions {
private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE
= "android:instantapps.installerbundle";
private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
private static final String KEY_REMOTE_ANIMATION_ADAPTER
= "android:activity.remoteAnimationAdapter";
/** @hide */
public static final int ANIM_NONE = 0;
@@ -268,6 +273,8 @@ public class ActivityOptions {
public static final int ANIM_CLIP_REVEAL = 11;
/** @hide */
public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
/** @hide */
public static final int ANIM_REMOTE_ANIMATION = 13;
private String mPackageName;
private Rect mLaunchBounds;
@@ -304,6 +311,7 @@ public class ActivityOptions {
private int mRotationAnimationHint = -1;
private Bundle mAppVerificationBundle;
private IAppTransitionAnimationSpecsFuture mSpecsFuture;
private RemoteAnimationAdapter mRemoteAnimationAdapter;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -826,6 +834,20 @@ public class ActivityOptions {
return opts;
}
/**
* Create an {@link ActivityOptions} instance that lets the application control the entire
* animation using a {@link RemoteAnimationAdapter}.
* @hide
*/
@RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
public static ActivityOptions makeRemoteAnimation(
RemoteAnimationAdapter remoteAnimationAdapter) {
final ActivityOptions opts = new ActivityOptions();
opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
opts.mAnimationType = ANIM_REMOTE_ANIMATION;
return opts;
}
/** @hide */
public boolean getLaunchTaskBehind() {
return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
@@ -922,6 +944,7 @@ public class ActivityOptions {
mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder(
KEY_SPECS_FUTURE));
}
mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER);
}
/**
@@ -1069,6 +1092,11 @@ public class ActivityOptions {
return mSpecsFuture;
}
/** @hide */
public RemoteAnimationAdapter getRemoteAnimationAdapter() {
return mRemoteAnimationAdapter;
}
/** @hide */
public static ActivityOptions fromBundle(Bundle bOptions) {
return bOptions != null ? new ActivityOptions(bOptions) : null;
@@ -1309,6 +1337,7 @@ public class ActivityOptions {
mAnimSpecs = otherOptions.mAnimSpecs;
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
}
/**
@@ -1403,7 +1432,9 @@ public class ActivityOptions {
if (mAppVerificationBundle != null) {
b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle);
}
if (mRemoteAnimationAdapter != null) {
b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter);
}
return b;
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2018 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 android.view;
/**
* Interface to be invoked by the controlling process when a remote animation has finished.
*
* @see IRemoteAnimationRunner
* {@hide}
*/
interface IRemoteAnimationFinishedCallback {
void onAnimationFinished();
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2018 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 android.view;
import android.view.RemoteAnimationTarget;
import android.view.IRemoteAnimationFinishedCallback;
/**
* Interface that is used to callback from window manager to the process that runs a remote
* animation to start or cancel it.
*
* {@hide}
*/
oneway interface IRemoteAnimationRunner {
/**
* Called when the process needs to start the remote animation.
*
* @param apps The list of apps to animate.
* @param finishedCallback The callback to invoke when the animation is finished.
*/
void onAnimationStart(in RemoteAnimationTarget[] apps,
in IRemoteAnimationFinishedCallback finishedCallback);
/**
* Called when the animation was cancelled. From this point on, any updates onto the leashes
* won't have any effect anymore.
*/
void onAnimationCancelled();
}

View File

@@ -38,6 +38,7 @@ import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedStackListener;
import android.view.RemoteAnimationAdapter;
import android.view.IRotationWatcher;
import android.view.IWallpaperVisibilityListener;
import android.view.IWindowSession;
@@ -124,6 +125,7 @@ interface IWindowManager
void overridePendingAppTransitionMultiThumbFuture(
IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
boolean scaleUp);
void overridePendingAppTransitionRemote(in RemoteAnimationAdapter remoteAnimationAdapter);
void executeAppTransition();
/** Used by system ui to report that recents has shown itself. */

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2018 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 android.view;
parcelable RemoteAnimationAdapter;

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2018 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 android.view;
import android.app.ActivityOptions;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Object that describes how to run a remote animation.
* <p>
* A remote animation lets another app control the entire app transition. It does so by
* <ul>
* <li>using {@link ActivityOptions#makeRemoteAnimation}</li>
* <li>using {@link IWindowManager#overridePendingAppTransitionRemote}</li>
* </ul>
* to register a {@link RemoteAnimationAdapter} that describes how the animation should be run:
* Along some meta-data, this object contains a callback that gets invoked from window manager when
* the transition is ready to be started.
* <p>
* Window manager supplies a list of {@link RemoteAnimationTarget}s into the callback. Each target
* contains information about the activity that is animating as well as
* {@link RemoteAnimationTarget#leash}. The controlling app can modify the leash like any other
* {@link SurfaceControl}, including the possibility to synchronize updating the leash's surface
* properties with a frame to be drawn using
* {@link SurfaceControl.Transaction#deferTransactionUntil}.
* <p>
* When the animation is done, the controlling app can invoke
* {@link IRemoteAnimationFinishedCallback} that gets supplied into
* {@link IRemoteAnimationRunner#onStartAnimation}
*
* @hide
*/
public class RemoteAnimationAdapter implements Parcelable {
private final IRemoteAnimationRunner mRunner;
private final long mDuration;
private final long mStatusBarTransitionDelay;
/**
* @param runner The interface that gets notified when we actually need to start the animation.
* @param duration The duration of the animation.
* @param statusBarTransitionDelay The desired delay for all visual animations in the
* status bar caused by this app animation in millis.
*/
public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
long statusBarTransitionDelay) {
mRunner = runner;
mDuration = duration;
mStatusBarTransitionDelay = statusBarTransitionDelay;
}
public RemoteAnimationAdapter(Parcel in) {
mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
mDuration = in.readLong();
mStatusBarTransitionDelay = in.readLong();
}
public IRemoteAnimationRunner getRunner() {
return mRunner;
}
public long getDuration() {
return mDuration;
}
public long getStatusBarTransitionDelay() {
return mStatusBarTransitionDelay;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongInterface(mRunner);
dest.writeLong(mDuration);
dest.writeLong(mStatusBarTransitionDelay);
}
public static final Creator<RemoteAnimationAdapter> CREATOR
= new Creator<RemoteAnimationAdapter>() {
public RemoteAnimationAdapter createFromParcel(Parcel in) {
return new RemoteAnimationAdapter(in);
}
public RemoteAnimationAdapter[] newArray(int size) {
return new RemoteAnimationAdapter[size];
}
};
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2018 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 android.view;
parcelable RemoteAnimationTarget;

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2018 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 android.view;
import android.annotation.IntDef;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Describes an activity to be animated as part of a remote animation.
*
* @hide
*/
public class RemoteAnimationTarget implements Parcelable {
/**
* The app is in the set of opening apps of this transition.
*/
public static final int MODE_OPENING = 0;
/**
* The app is in the set of closing apps of this transition.
*/
public static final int MODE_CLOSING = 1;
@IntDef(prefix = { "MODE_" }, value = {
MODE_OPENING,
MODE_CLOSING
})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {}
/**
* The {@link Mode} to describe whether this app is opening or closing.
*/
public final @Mode int mode;
/**
* The id of the task this app belongs to.
*/
public final int taskId;
/**
* The {@link SurfaceControl} object to actually control the transform of the app.
*/
public final SurfaceControl leash;
/**
* Whether the app is translucent and may reveal apps behind.
*/
public final boolean isTranslucent;
/**
* The clip rect window manager applies when clipping the app's main surface in screen space
* coordinates. This is just a hint to the animation runner: If running a clip-rect animation,
* anything that extends beyond these bounds will not have any effect. This implies that any
* clip-rect animation should likely stop at these bounds.
*/
public final Rect clipRect;
/**
* The index of the element in the tree in prefix order. This should be used for z-layering
* to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
* happen.
*/
public final int prefixOrderIndex;
/**
* The source position of the app, in screen spaces coordinates. If the position of the leash
* is modified from the controlling app, any animation transform needs to be offset by this
* amount.
*/
public final Point position;
/**
* The bounds of the source container the app lives in, in screen space coordinates. If the crop
* of the leash is modified from the controlling app, it needs to take the source container
* bounds into account when calculating the crop.
*/
public final Rect sourceContainerBounds;
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
this.isTranslucent = isTranslucent;
this.clipRect = new Rect(clipRect);
this.prefixOrderIndex = prefixOrderIndex;
this.position = new Point(position);
this.sourceContainerBounds = new Rect(sourceContainerBounds);
}
public RemoteAnimationTarget(Parcel in) {
taskId = in.readInt();
mode = in.readInt();
leash = in.readParcelable(null);
isTranslucent = in.readBoolean();
clipRect = in.readParcelable(null);
prefixOrderIndex = in.readInt();
position = in.readParcelable(null);
sourceContainerBounds = in.readParcelable(null);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(taskId);
dest.writeInt(mode);
dest.writeParcelable(leash, 0 /* flags */);
dest.writeBoolean(isTranslucent);
dest.writeParcelable(clipRect, 0 /* flags */);
dest.writeInt(prefixOrderIndex);
dest.writeParcelable(position, 0 /* flags */);
dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
}
public static final Creator<RemoteAnimationTarget> CREATOR
= new Creator<RemoteAnimationTarget>() {
public RemoteAnimationTarget createFromParcel(Parcel in) {
return new RemoteAnimationTarget(in);
}
public RemoteAnimationTarget[] newArray(int size) {
return new RemoteAnimationTarget[size];
}
};
}

View File

@@ -3692,6 +3692,12 @@
<permission android:name="android.permission.MODIFY_QUIET_MODE"
android:protectionLevel="signature|privileged" />
<!-- Allows an application to control remote animations. See
{@link ActivityOptions#makeRemoteAnimation}
@hide <p>Not for use by third-party applications. -->
<permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
android:protectionLevel="signature|privileged" />
<application android:process="system"
android:persistent="true"
android:hasCode="false"

View File

@@ -75,6 +75,7 @@ applications that come with the platform
<privapp-permissions package="com.android.launcher3">
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
</privapp-permissions>

View File

@@ -21,6 +21,7 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
@@ -1481,6 +1482,10 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
case ANIM_OPEN_CROSS_PROFILE_APPS:
service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps();
break;
case ANIM_REMOTE_ANIMATION:
service.mWindowManager.overridePendingAppTransitionRemote(
pendingOptions.getRemoteAnimationAdapter());
break;
default:
Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType);
break;

View File

@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.Manifest.permission.ACTIVITY_EMBEDDING;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -162,6 +163,7 @@ import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.RemoteAnimationAdapter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
@@ -1680,6 +1682,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
// Check permission for remote animations
final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter();
if (adapter != null && mService.checkPermission(
CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid)
!= PERMISSION_GRANTED) {
final String msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ") with remoteAnimationAdapter";
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
return true;

View File

@@ -75,6 +75,7 @@ import android.util.proto.ProtoOutputStream;
import android.view.AppTransitionAnimationSpec;
import android.view.DisplayListCanvas;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.RemoteAnimationAdapter;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
import android.view.WindowManager;
@@ -213,6 +214,8 @@ public class AppTransition implements Dump {
* }.
*/
private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9;
private static final int NEXT_TRANSIT_TYPE_REMOTE = 10;
private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
// These are the possible states for the enter/exit activities during a thumbnail transition
@@ -275,6 +278,8 @@ public class AppTransition implements Dump {
private final boolean mGridLayoutRecentsEnabled;
private final boolean mLowRamRecentsEnabled;
private RemoteAnimationController mRemoteAnimationController;
AppTransition(Context context, WindowManagerService service) {
mContext = context;
mService = service;
@@ -454,6 +459,9 @@ public class AppTransition implements Dump {
app.startDelayingAnimationStart();
}
}
if (mRemoteAnimationController != null) {
mRemoteAnimationController.goodToGo();
}
return redoLayout;
}
@@ -468,6 +476,7 @@ public class AppTransition implements Dump {
mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
mNextAppTransitionPackage = null;
mNextAppTransitionAnimationsSpecs.clear();
mRemoteAnimationController = null;
mNextAppTransitionAnimationsSpecsFuture = null;
mDefaultNextAppTransitionAnimationSpec = null;
mAnimationFinishedCallback = null;
@@ -1551,6 +1560,10 @@ public class AppTransition implements Dump {
&& mNextAppTransition != TRANSIT_KEYGUARD_GOING_AWAY;
}
RemoteAnimationController getRemoteAnimationController() {
return mRemoteAnimationController;
}
/**
*
* @param frame These are the bounds of the window when it finishes the animation. This is where
@@ -1788,7 +1801,7 @@ public class AppTransition implements Dump {
void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
IRemoteCallback startedCallback) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
mNextAppTransitionPackage = packageName;
@@ -1796,14 +1809,12 @@ public class AppTransition implements Dump {
mNextAppTransitionExit = exitAnim;
postAnimationCallback();
mNextAppTransitionCallback = startedCallback;
} else {
postAnimationCallback();
}
}
void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
int startHeight) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
@@ -1813,7 +1824,7 @@ public class AppTransition implements Dump {
void overridePendingAppTransitionClipReveal(int startX, int startY,
int startWidth, int startHeight) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
@@ -1823,7 +1834,7 @@ public class AppTransition implements Dump {
void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY,
IRemoteCallback startedCallback, boolean scaleUp) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
: NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
@@ -1831,14 +1842,12 @@ public class AppTransition implements Dump {
putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb);
postAnimationCallback();
mNextAppTransitionCallback = startedCallback;
} else {
postAnimationCallback();
}
}
void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX, int startY,
int targetWidth, int targetHeight, IRemoteCallback startedCallback, boolean scaleUp) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
: NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
@@ -1847,15 +1856,13 @@ public class AppTransition implements Dump {
srcThumb);
postAnimationCallback();
mNextAppTransitionCallback = startedCallback;
} else {
postAnimationCallback();
}
}
public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
boolean scaleUp) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
: NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
@@ -1878,15 +1885,13 @@ public class AppTransition implements Dump {
postAnimationCallback();
mNextAppTransitionCallback = onAnimationStartedCallback;
mAnimationFinishedCallback = onAnimationFinishedCallback;
} else {
postAnimationCallback();
}
}
void overridePendingAppTransitionMultiThumbFuture(
IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
boolean scaleUp) {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
: NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
@@ -1896,14 +1901,21 @@ public class AppTransition implements Dump {
}
}
void overrideInPlaceAppTransition(String packageName, int anim) {
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
if (isTransitionSet()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService,
remoteAnimationAdapter, mService.mH);
}
}
void overrideInPlaceAppTransition(String packageName, int anim) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
mNextAppTransitionPackage = packageName;
mNextAppTransitionInPlace = anim;
} else {
postAnimationCallback();
}
}
@@ -1911,13 +1923,18 @@ public class AppTransition implements Dump {
* @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS}
*/
void overridePendingAppTransitionStartCrossProfileApps() {
if (isTransitionSet()) {
if (canOverridePendingAppTransition()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
postAnimationCallback();
}
}
private boolean canOverridePendingAppTransition() {
// Remote animations always take precedence
return isTransitionSet() && mNextAppTransitionType != NEXT_TRANSIT_TYPE_REMOTE;
}
/**
* If a future is set for the app transition specs, fetch it in another thread.
*/

View File

@@ -378,7 +378,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
// Reset the state of mHiddenSetFromTransferredStartingWindow since visibility is actually
// been set by the app now.
mHiddenSetFromTransferredStartingWindow = false;
setClientHidden(!visible);
// Allow for state changes and animation to be applied if:
// * token is transitioning visibility state
@@ -463,6 +462,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
mService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(token);
}
// Update the client visibility if we are not running an animation. Otherwise, we'll
// update client visibility state in onAnimationFinished.
if (!visible && !delayed) {
setClientHidden(true);
}
// If we are hidden but there is no delay needed we immediately
// apply the Surface transaction so that the ActivityManager
// can have some guarantee on the Surface state following
@@ -1611,27 +1616,37 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
// different animation is running.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
if (okToAnimate()) {
final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
if (a != null) {
final TaskStack stack = getStack();
mTmpPoint.set(0, 0);
mTmpRect.setEmpty();
if (stack != null) {
stack.getRelativePosition(mTmpPoint);
stack.getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
}
final AnimationAdapter adapter = new LocalAnimationAdapter(
new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
mService.mAppTransition.canSkipFirstFrame(),
mService.mAppTransition.getAppStackClipMode()),
mService.mSurfaceAnimationRunner);
if (a.getZAdjustment() == Animation.ZORDER_TOP) {
mNeedsZBoost = true;
final AnimationAdapter adapter;
final TaskStack stack = getStack();
mTmpPoint.set(0, 0);
mTmpRect.setEmpty();
if (stack != null) {
stack.getRelativePosition(mTmpPoint);
stack.getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
}
if (mService.mAppTransition.getRemoteAnimationController() != null) {
adapter = mService.mAppTransition.getRemoteAnimationController()
.createAnimationAdapter(this, mTmpPoint, mTmpRect);
} else {
final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
if (a != null) {
adapter = new LocalAnimationAdapter(
new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
mService.mAppTransition.canSkipFirstFrame(),
mService.mAppTransition.getAppStackClipMode()),
mService.mSurfaceAnimationRunner);
if (a.getZAdjustment() == Animation.ZORDER_TOP) {
mNeedsZBoost = true;
}
mTransit = transit;
mTransitFlags = mService.mAppTransition.getTransitFlags();
} else {
adapter = null;
}
}
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible());
mTransit = transit;
mTransitFlags = mService.mAppTransition.getTransitFlags();
}
} else {
cancelAnimation();
@@ -1754,6 +1769,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
"AppWindowToken");
clearThumbnail();
setClientHidden(isHidden());
if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
getDisplayContent().computeImeTarget(true /* updateImeTarget */);

View File

@@ -0,0 +1,206 @@
/*
* Copyright (C) 2018 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.server.wm;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationFinishedCallback.Stub;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.util.ArrayList;
/**
* Helper class to run app animations in a remote process.
*/
class RemoteAnimationController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM;
private static final long TIMEOUT_MS = 2000;
private final WindowManagerService mService;
private final RemoteAnimationAdapter mRemoteAnimationAdapter;
private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>();
private final Rect mTmpRect = new Rect();
private final Handler mHandler;
private final IRemoteAnimationFinishedCallback mFinishedCallback = new Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
RemoteAnimationController.this.onAnimationFinished();
}
};
private final Runnable mTimeoutRunnable = () -> {
onAnimationFinished();
invokeAnimationCancelled();
};
RemoteAnimationController(WindowManagerService service,
RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) {
mService = service;
mRemoteAnimationAdapter = remoteAnimationAdapter;
mHandler = handler;
}
/**
* Creates an animation for each individual {@link AppWindowToken}.
*
* @param appWindowToken The app to animate.
* @param position The position app bounds, in screen coordinates.
* @param stackBounds The stack bounds of the app.
* @return The adapter to be run on the app.
*/
AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
Rect stackBounds) {
final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
appWindowToken, position, stackBounds);
mPendingAnimations.add(adapter);
return adapter;
}
/**
* Called when the transition is ready to be started, and all leashes have been set up.
*/
void goodToGo() {
mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
try {
mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(),
mFinishedCallback);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to start remote animation", e);
onAnimationFinished();
}
}
private RemoteAnimationTarget[] createAnimations() {
final RemoteAnimationTarget[] result = new RemoteAnimationTarget[mPendingAnimations.size()];
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
result[i] = mPendingAnimations.get(i).createRemoteAppAnimation();
}
return result;
}
private void onAnimationFinished() {
mHandler.removeCallbacks(mTimeoutRunnable);
synchronized (mService.mWindowMap) {
mService.openSurfaceTransaction();
try {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
}
} finally {
mService.closeSurfaceTransaction("RemoteAnimationController#finished");
}
}
}
private void invokeAnimationCancelled() {
try {
mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify cancel", e);
}
}
private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
private final AppWindowToken mAppWindowToken;
private SurfaceControl mCapturedLeash;
private OnAnimationFinishedCallback mCapturedFinishCallback;
private final Point mPosition = new Point();
private final Rect mStackBounds = new Rect();
RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position,
Rect stackBounds) {
mAppWindowToken = appWindowToken;
mPosition.set(position.x, position.y);
mStackBounds.set(stackBounds);
}
RemoteAnimationTarget createRemoteAppAnimation() {
return new RemoteAnimationTarget(mAppWindowToken.getTask().mTaskId, getMode(),
mCapturedLeash, !mAppWindowToken.fillsParent(),
mAppWindowToken.findMainWindow().mWinAnimator.mLastClipRect,
mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds);
}
private int getMode() {
if (mService.mOpeningApps.contains(mAppWindowToken)) {
return RemoteAnimationTarget.MODE_OPENING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
}
}
@Override
public boolean getDetachWallpaper() {
return false;
}
@Override
public int getBackgroundColor() {
return 0;
}
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
OnAnimationFinishedCallback finishCallback) {
// Restore z-layering, position and stack crop until client has a chance to modify it.
t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex());
t.setPosition(animationLeash, mPosition.x, mPosition.y);
mTmpRect.set(mStackBounds);
mTmpRect.offsetTo(0, 0);
t.setWindowCrop(animationLeash, mTmpRect);
mCapturedLeash = animationLeash;
mCapturedFinishCallback = finishCallback;
}
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
mPendingAnimations.remove(this);
if (mPendingAnimations.isEmpty()) {
mHandler.removeCallbacks(mTimeoutRunnable);
invokeAnimationCancelled();
}
}
@Override
public long getDurationHint() {
return mRemoteAnimationAdapter.getDuration();
}
@Override
public long getStatusBarTransitionsStartTime() {
return SystemClock.uptimeMillis()
+ mRemoteAnimationAdapter.getStatusBarTransitionDelay();
}
}
}

View File

@@ -16,6 +16,7 @@
package com.android.server.wm;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
@@ -210,6 +211,7 @@ import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.RemoteAnimationAdapter;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
@@ -2623,6 +2625,18 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
@Override
public void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
if (!checkCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
"overridePendingAppTransitionRemote()")) {
throw new SecurityException(
"Requires CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission");
}
synchronized (mWindowMap) {
mAppTransition.overridePendingAppTransitionRemote(remoteAnimationAdapter);
}
}
@Override
public void endProlongedAnimations() {
synchronized (mWindowMap) {

View File

@@ -193,6 +193,7 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputWindowHandle;
import com.android.server.policy.WindowManagerPolicy;
@@ -3952,6 +3953,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return null;
}
/**
* @return True if we our one of our ancestors has {@link #mAnimatingExit} set to true, false
* otherwise.
*/
@VisibleForTesting
boolean isSelfOrAncestorWindowAnimatingExit() {
WindowState window = this;
do {
if (window.mAnimatingExit) {
return true;
}
window = window.getParentWindow();
} while (window != null);
return false;
}
void onExitAnimationDone() {
if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
+ ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
@@ -3987,7 +4004,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
if (!mAnimatingExit) {
if (!isSelfOrAncestorWindowAnimatingExit()) {
return;
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2018 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.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Postsubmit;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest
*/
@SmallTest
@FlakyTest(detail = "Promote to presubmit if non-flakyness is established")
@RunWith(AndroidJUnit4.class)
public class RemoteAnimationControllerTest extends WindowTestsBase {
@Mock SurfaceControl mMockLeash;
@Mock Transaction mMockTransaction;
@Mock OnAnimationFinishedCallback mFinishedCallback;
@Mock IRemoteAnimationRunner mMockRunner;
private RemoteAnimationAdapter mAdapter;
private RemoteAnimationController mController;
private final OffsettableClock mClock = new OffsettableClock.Stopped();
private TestHandler mHandler;
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50);
sWm.mH.runWithScissors(() -> {
mHandler = new TestHandler(null, mClock);
}, 0);
mController = new RemoteAnimationController(sWm, mAdapter, mHandler);
}
@Test
public void testRun() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
sWm.mOpeningApps.add(win.mAppToken);
try {
final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
new Point(50, 100), new Rect(50, 100, 150, 150));
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
assertEquals(1, appsCaptor.getValue().length);
final RemoteAnimationTarget app = appsCaptor.getValue()[0];
assertEquals(new Point(50, 100), app.position);
assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex);
assertEquals(win.mAppToken.getTask().mTaskId, app.taskId);
assertEquals(mMockLeash, app.leash);
assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
assertEquals(false, app.isTranslucent);
verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex);
verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 100, 50));
finishedCaptor.getValue().onAnimationFinished();
verify(mFinishedCallback).onAnimationFinished(eq(adapter));
} finally {
sWm.mOpeningApps.clear();
}
}
@Test
public void testCancel() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
new Point(50, 100), new Rect(50, 100, 150, 150));
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
adapter.onAnimationCancelled(mMockLeash);
verify(mMockRunner).onAnimationCancelled();
}
@Test
public void testTimeout() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
new Point(50, 100), new Rect(50, 100, 150, 150));
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
mClock.fastForward(2500);
mHandler.timeAdvance();
verify(mMockRunner).onAnimationCancelled();
verify(mFinishedCallback).onAnimationFinished(eq(adapter));
}
}

View File

@@ -223,6 +223,19 @@ public class WindowStateTests extends WindowTestsBase {
assertFalse(app.canAffectSystemUiFlags());
}
@Test
public void testIsSelfOrAncestorWindowAnimating() throws Exception {
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2");
assertFalse(child2.isSelfOrAncestorWindowAnimatingExit());
child2.mAnimatingExit = true;
assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
child2.mAnimatingExit = false;
root.mAnimatingExit = true;
assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
}
private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) {
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;