Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7bd5987 authored by Evan Rosky's avatar Evan Rosky Committed by Android (Google) Code Review
Browse files

Merge "Add support for remote transition playback in shell transitions"

parents b1dc062e 30f3544f
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.window.IRemoteTransition;
import android.window.WindowContainerToken;

import java.lang.annotation.Retention;
@@ -298,6 +299,8 @@ public class ActivityOptions {
    private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
    private static final String KEY_REMOTE_ANIMATION_ADAPTER
            = "android:activity.remoteAnimationAdapter";
    private static final String KEY_REMOTE_TRANSITION =
            "android:activity.remoteTransition";

    /**
     * @see #setLaunchCookie
@@ -380,6 +383,7 @@ public class ActivityOptions {
    private IAppTransitionAnimationSpecsFuture mSpecsFuture;
    private RemoteAnimationAdapter mRemoteAnimationAdapter;
    private IBinder mLaunchCookie;
    private IRemoteTransition mRemoteTransition;

    /**
     * Create an ActivityOptions specifying a custom animation to run when
@@ -959,6 +963,21 @@ 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,
            IRemoteTransition remoteTransition) {
        final ActivityOptions opts = new ActivityOptions();
        opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
        opts.mAnimationType = ANIM_REMOTE_ANIMATION;
        opts.mRemoteTransition = remoteTransition;
        return opts;
    }

    /** @hide */
    public boolean getLaunchTaskBehind() {
        return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
@@ -1064,6 +1083,8 @@ public class ActivityOptions {
        }
        mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER);
        mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE);
        mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
                KEY_REMOTE_TRANSITION));
    }

    /**
@@ -1222,6 +1243,11 @@ public class ActivityOptions {
        mRemoteAnimationAdapter = remoteAnimationAdapter;
    }

    /** @hide */
    public IRemoteTransition getRemoteTransition() {
        return mRemoteTransition;
    }

    /** @hide */
    public static ActivityOptions fromBundle(Bundle bOptions) {
        return bOptions != null ? new ActivityOptions(bOptions) : null;
@@ -1724,6 +1750,9 @@ public class ActivityOptions {
        if (mLaunchCookie != null) {
            b.putBinder(KEY_LAUNCH_COOKIE, mLaunchCookie);
        }
        if (mRemoteTransition != null) {
            b.putBinder(KEY_REMOTE_TRANSITION, mRemoteTransition.asBinder());
        }
        return b;
    }

+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.window;

import android.view.IRemoteAnimationFinishedCallback;
import android.view.SurfaceControl;
import android.window.TransitionInfo;

/**
 * Interface allowing remote processes to play transition animations.
 * The usage flow is as follows:
 * <p><ol>
 *  <li>The remote tags a lifecycle event with an IRemoteTransition (via a parameter in
 *      ActivityOptions#makeRemoteAnimation) or a transition matches a filter registered via
 *      Transitions#registerRemote.
 *  <li>Shell then associates the transition for the event with the IRemoteTransition
 *  <li>Shell receives onTransitionReady and delegates the animation to the IRemoteTransition
 *      via {@link #startAnimation}.
 *  <li>Once the IRemoteTransition is done animating, it will call the finishCallback.
 *  <li>Shell/Core finish-up the transition.
 * </ul>
 *
 * {@hide}
 */
oneway interface IRemoteTransition {
    /**
     * Starts a transition animation. Once complete, the implementation should call
     * `finishCallback`.
     */
    void startAnimation(in TransitionInfo info, in SurfaceControl.Transaction t,
            in IRemoteAnimationFinishedCallback finishCallback);
}
+3 −7
Original line number Diff line number Diff line
@@ -16,10 +16,9 @@

package android.window;

import android.app.ActivityManager;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
import android.window.TransitionRequestInfo;

/**
 * Implemented by WMShell to initiate and play transition animations.
@@ -56,12 +55,9 @@ oneway interface ITransitionPlayer {
     * Called when something in WMCore requires a transition to play -- for example when an Activity
     * is started in a new Task.
     *
     * @param type The {@link WindowManager#TransitionType} of the transition to start.
     * @param transitionToken An identifying token for the transition that needs to be started.
     *                        Pass this to {@link IWindowOrganizerController#startTransition}.
     * @param triggerTask If non-null, the task containing the activity whose lifecycle change
     *                    (start or finish) has caused this transition to occur.
     * @param request Information about this particular request.
     */
    void requestStartTransition(int type, in IBinder transitionToken,
            in ActivityManager.RunningTaskInfo triggerTask);
    void requestStartTransition(in IBinder transitionToken, in TransitionRequestInfo request);
}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.window;

parcelable TransitionFilter;
+215 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.window;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * A parcelable filter that can be used for rerouting transitions to a remote. This is a local
 * representation so that the transition system doesn't need to make blocking queries over
 * binder.
 *
 * @hide
 */
public final class TransitionFilter implements Parcelable {

    /**
     * When non-null: this is a list of transition types that this filter applies to. This filter
     * will fail for transitions that aren't one of these types.
     */
    @Nullable public int[] mTypeSet = null;

    /**
     * A list of required changes. To pass, a transition must meet all requirements.
     */
    @Nullable public Requirement[] mRequirements = null;

    public TransitionFilter() {
    }

    private TransitionFilter(Parcel in) {
        mTypeSet = in.createIntArray();
        mRequirements = in.createTypedArray(Requirement.CREATOR);
    }

    /** @return true if `info` meets all the requirements to pass this filter. */
    public boolean matches(@NonNull TransitionInfo info) {
        if (mTypeSet != null) {
            // non-null typeset, so make sure info is one of the types.
            boolean typePass = false;
            for (int i = 0; i < mTypeSet.length; ++i) {
                if (info.getType() == mTypeSet[i]) {
                    typePass = true;
                    break;
                }
            }
            if (!typePass) return false;
        }
        // Make sure info meets all of the requirements.
        if (mRequirements != null) {
            for (int i = 0; i < mRequirements.length; ++i) {
                if (!mRequirements[i].matches(info)) return false;
            }
        }
        return true;
    }

    @Override
    /** @hide */
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeIntArray(mTypeSet);
        dest.writeTypedArray(mRequirements, flags);
    }

    @NonNull
    public static final Creator<TransitionFilter> CREATOR =
            new Creator<TransitionFilter>() {
                @Override
                public TransitionFilter createFromParcel(Parcel in) {
                    return new TransitionFilter(in);
                }

                @Override
                public TransitionFilter[] newArray(int size) {
                    return new TransitionFilter[size];
                }
            };

    @Override
    /** @hide */
    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{types=[");
        if (mTypeSet != null) {
            for (int i = 0; i < mTypeSet.length; ++i) {
                sb.append((i == 0 ? "" : ",") + mTypeSet[i]);
            }
        }
        sb.append("] checks=[");
        if (mRequirements != null) {
            for (int i = 0; i < mRequirements.length; ++i) {
                sb.append((i == 0 ? "" : ",") + mRequirements[i]);
            }
        }
        return sb.append("]}").toString();
    }

    /**
     * Matches a change that a transition must contain to pass this filter. All requirements in a
     * filter must be met to pass the filter.
     */
    public static final class Requirement implements Parcelable {
        public int mActivityType = ACTIVITY_TYPE_UNDEFINED;
        public int[] mModes = null;

        public Requirement() {
        }

        private Requirement(Parcel in) {
            mActivityType = in.readInt();
            mModes = in.createIntArray();
        }

        /** Go through changes and find if at-least one change matches this filter */
        boolean matches(@NonNull TransitionInfo info) {
            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                final TransitionInfo.Change change = info.getChanges().get(i);
                if (change.getParent() != null) {
                    // Only look at the top animating windows.
                    continue;
                }
                if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
                    if (change.getTaskInfo() == null
                            || change.getTaskInfo().getActivityType() != mActivityType) {
                        continue;
                    }
                }
                if (mModes != null) {
                    boolean pass = false;
                    for (int m = 0; m < mModes.length; ++m) {
                        if (mModes[m] == change.getMode()) {
                            pass = true;
                            break;
                        }
                    }
                    if (!pass) continue;
                }
                return true;
            }
            return false;
        }

        /** Check if the request matches this filter. It may generate false positives */
        boolean matches(@NonNull TransitionRequestInfo request) {
            // Can't check modes since the transition hasn't been built at this point.
            if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
            return request.getTriggerTask() != null
                    && request.getTriggerTask().getActivityType() == mActivityType;
        }

        @Override
        /** @hide */
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mActivityType);
            dest.writeIntArray(mModes);
        }

        @NonNull
        public static final Creator<Requirement> CREATOR =
                new Creator<Requirement>() {
                    @Override
                    public Requirement createFromParcel(Parcel in) {
                        return new Requirement(in);
                    }

                    @Override
                    public Requirement[] newArray(int size) {
                        return new Requirement[size];
                    }
                };

        @Override
        /** @hide */
        public int describeContents() {
            return 0;
        }

        @Override
        public String toString() {
            StringBuilder out = new StringBuilder();
            out.append("{atype=" + WindowConfiguration.activityTypeToString(mActivityType));
            out.append(" modes=[");
            if (mModes != null) {
                for (int i = 0; i < mModes.length; ++i) {
                    out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
                }
            }
            return out.append("]}").toString();
        }
    }
}
Loading