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

Commit 2c206400 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Implementing an unfold animation using CannedAnimationController

Bug: 297057373
Flag: aconfig enable_unfold_state_animation DISABLED
Test: Manual
Change-Id: Iaa352133ebf06ab74b9ba4b8c4d3523f27666089
parent a3e2c597
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -127,3 +127,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;
@@ -955,10 +958,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