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

Commit 7ec2e11b authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Implementing an unfold animation using CannedAnimationController" into main

parents e6760e68 2c206400
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -133,3 +133,10 @@ flag {
    description: "Enables asnc inflation of workspace icons"
    bug: "318539160"
}

flag {
    name: "enable_unfold_state_animation"
    namespace: "launcher"
    description: "Tie unfold animation with state animation"
    bug: "297057373"
}
+7 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;

import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
@@ -405,8 +406,12 @@ public class TaskbarManager {
     */
    private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
            StatefulActivity activity) {
        if (activity instanceof QuickstepLauncher) {
            return ((QuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
        if (!enableUnfoldStateAnimation()) {
            if (activity instanceof QuickstepLauncher ql) {
                return ql.getUnfoldTransitionProgressProvider();
            }
        } else {
            return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider();
        }
        return null;
    }
+14 −3
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEAS
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;

import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
@@ -170,6 +171,8 @@ import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.util.unfold.LauncherUnfoldTransitionController;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -965,10 +968,18 @@ public class QuickstepLauncher extends Launcher {
    }

    private void initUnfoldTransitionProgressProvider() {
        if (!enableUnfoldStateAnimation()) {
            final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
            if (config.isEnabled()) {
                initRemotelyCalculatedUnfoldAnimation(config);
            }
        } else {
            ProxyUnfoldTransitionProvider provider =
                    SystemUiProxy.INSTANCE.get(this).getUnfoldTransitionProvider();
            if (provider != null) {
                new LauncherUnfoldTransitionController(this, provider);
            }
        }
    }

    /** Receives animation progress from sysui process. */
+30 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.quickstep;

import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;

import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -67,6 +68,7 @@ import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistUtils;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -74,6 +76,7 @@ import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
@@ -177,7 +180,10 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
     */
    private final PendingIntent mRecentsPendingIntent;

    public SystemUiProxy(Context context) {
    @Nullable
    private final ProxyUnfoldTransitionProvider mUnfoldTransitionProvider;

    private SystemUiProxy(Context context) {
        mContext = context;
        mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
        final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
@@ -187,6 +193,10 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
        mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
                        | Intent.FILL_IN_COMPONENT, options.toBundle());

        mUnfoldTransitionProvider =
                (enableUnfoldStateAnimation() && new ResourceUnfoldTransitionConfig().isEnabled())
                         ? new ProxyUnfoldTransitionProvider() : null;
    }

    @Override
@@ -251,7 +261,7 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
        mRecentTasks = recentTasks;
        mBackAnimation = backAnimation;
        mDesktopMode = desktopMode;
        mUnfoldAnimation = unfoldAnimation;
        mUnfoldAnimation = enableUnfoldStateAnimation() ? null : unfoldAnimation;
        mDragAndDrop = dragAndDrop;
        linkToDeath();
        // re-attach the listeners once missing due to setProxy has not been initialized yet.
@@ -272,6 +282,19 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
        setAssistantOverridesRequested(
                AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
        mStateChangeCallbacks.forEach(Runnable::run);

        if (mUnfoldTransitionProvider != null) {
            if (unfoldAnimation != null) {
                try {
                    unfoldAnimation.setListener(mUnfoldTransitionProvider);
                    mUnfoldTransitionProvider.setActive(true);
                } catch (RemoteException e) {
                    // Ignore
                }
            } else {
                mUnfoldTransitionProvider.setActive(false);
            }
        }
    }

    /**
@@ -1451,6 +1474,11 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
        }
    }

    @Nullable
    public ProxyUnfoldTransitionProvider getUnfoldTransitionProvider() {
        return mUnfoldTransitionProvider;
    }

    //
    // Recents
    //
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.quickstep.util.unfold

import android.app.Activity
import android.os.Trace
import android.view.Surface
import com.android.launcher3.Alarm
import com.android.launcher3.DeviceProfile
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
import com.android.launcher3.Launcher
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener

/** Controls animations that are happening during unfolding foldable devices */
class LauncherUnfoldTransitionController(
    private val launcher: Launcher,
    private val progressProvider: ProxyUnfoldTransitionProvider
) : OnDeviceProfileChangeListener, ActivityLifecycleCallbacksAdapter, TransitionProgressListener {

    private var isTablet: Boolean? = null
    private var hasUnfoldTransitionStarted = false
    private val timeoutAlarm =
        Alarm().apply {
            setOnAlarmListener {
                onTransitionFinished()
                Trace.endAsyncSection("$TAG#startedPreemptively", 0)
            }
        }

    init {
        launcher.addOnDeviceProfileChangeListener(this)
        launcher.registerActivityLifecycleCallbacks(this)
    }

    override fun onActivityPaused(activity: Activity) {
        progressProvider.removeCallback(this)
    }

    override fun onActivityResumed(activity: Activity) {
        progressProvider.addCallback(this)
    }

    override fun onDeviceProfileChanged(dp: DeviceProfile) {
        if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) {
            return
        }

        if (isTablet != null && dp.isTablet != isTablet) {
            // We should preemptively start the animation only if:
            // - We changed to the unfolded screen
            // - SystemUI IPC connection is alive, so we won't end up in a situation that we won't
            //   receive transition progress events from SystemUI later because there was no
            //   IPC connection established (e.g. because of SystemUI crash)
            // - SystemUI has not already sent unfold animation progress events. This might happen
            //   if Launcher was not open during unfold, in this case we receive the configuration
            //   change only after we went back to home screen and we don't want to start the
            //   animation in this case.
            if (dp.isTablet && progressProvider.isActive && !hasUnfoldTransitionStarted) {
                // Preemptively start the unfold animation to make sure that we have drawn
                // the first frame of the animation before the screen gets unblocked
                onTransitionStarted()
                Trace.beginAsyncSection("$TAG#startedPreemptively", 0)
                timeoutAlarm.setAlarm(PREEMPTIVE_UNFOLD_TIMEOUT_MS)
            }
            if (!dp.isTablet) {
                // Reset unfold transition status when folded
                hasUnfoldTransitionStarted = false
            }
        }

        isTablet = dp.isTablet
    }

    override fun onTransitionStarted() {
        hasUnfoldTransitionStarted = true
        launcher.animationCoordinator.setAnimation(
            provider = this,
            factory = this::onPrepareUnfoldAnimation,
            duration =
                1000L // The expected duration for the animation. Then only comes to play if we have
            // to run the animation ourselves in case sysui misses the end signal
        )
        timeoutAlarm.cancelAlarm()
    }

    override fun onTransitionProgress(progress: Float) {
        hasUnfoldTransitionStarted = true
        launcher.animationCoordinator.getPlaybackController(this)?.setPlayFraction(progress)
    }

    override fun onTransitionFinished() {
        // Run the animation to end the animation in case it is not already at end progress. It
        // will scale the duration to the remaining progress
        launcher.animationCoordinator.getPlaybackController(this)?.start()
        timeoutAlarm.cancelAlarm()
    }

    private fun onPrepareUnfoldAnimation(anim: PendingAnimation) {
        val dp = launcher.deviceProfile
        val rotation = dp.displayInfo.rotation
        val isVertical = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
        UnfoldAnimationBuilder.buildUnfoldAnimation(
            launcher,
            isVertical,
            dp.displayInfo.currentSize,
            anim
        )
    }

    companion object {
        private const val TAG = "LauncherUnfoldTransitionController"
    }
}
Loading