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

Commit 6bb0ca6f authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

System Perf Hints in PiP

Use system perf hints during PiP
interactions to avoid animations at low refresh rates.

The latest patchset introduces a new wrapper around
HighPerfSession to make session management safer and easier
for PiP component.

Namely, we introduce the following two helper classes:

 - PipPerfHintController:

	A wrapper around SystemPerfHintController to start sessions
	with an interface allowing registering optional timeouts with
	timeout callbacks.

	This should act as a safeguard, especially for complex interactions.
	For now this safeguard is only used for logging as a preventative
	measure against introducing any regressions to avoid
	"high performance hint starvation".

 - PipPerfHintController.PipHighPerfSession:

	A wrapper around HighPerfSession that helps keep track of
	active sessions present.
	PipHighPerfSession also makes sure necessary updates are made
	to cleanup if the session is either closed directly or
	if it is garbage collected.

	For example, if the session client loses all strong references
	to the session returned by PipPerfHintController#startSession(),
	the GC reclaims the memory, and we close the session internally.
	Moreover, an internal WeakHashMap is updated to avoid calling
	timeout callback if one was registered
	(these callbacks pass in a reference to the session,
	in case a client uses multiple).

As a side note, manual testing has shown so far that all PiP transition
animations are by default at maximum refresh rate of 120Hz; hence, this CL only
addresses non-Transitions related PiP CUJs, like double taps,
pinch-to-resize, drags, stashes and unstashes.

Bug: 304564014
Test: manually testing CUJs listed above

Change-Id: I5da7f4dad0d2934be4c44ea37e8027ad83506894
parent 2183a65d
Loading
Loading
Loading
Loading
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.wm.shell.common.pip;

import static android.window.SystemPerformanceHinter.HINT_SF;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;

import android.window.SystemPerformanceHinter;
import android.window.SystemPerformanceHinter.HighPerfSession;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;

import java.io.PrintWriter;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Consumer;

/**
 * Manages system performance hints for PiP CUJs and interactions.
 */
public class PipPerfHintController {
    private static final String TAG = PipPerfHintController.class.getSimpleName();

    // Delay until signal about a session cleanup is sent.
    private static final int SESSION_TIMEOUT_DELAY = 20_000;

    // Maximum number of possible high perf session.
    private static final int SESSION_POOL_SIZE = 20;

    private final SystemPerformanceHinter mSystemPerformanceHinter;
    @NonNull
    private final PipDisplayLayoutState mPipDisplayLayoutState;
    @NonNull
    private final ShellExecutor mMainExecutor;


    public PipPerfHintController(@NonNull PipDisplayLayoutState pipDisplayLayoutState,
            @ShellMainThread ShellExecutor mainExecutor,
            @NonNull SystemPerformanceHinter systemPerformanceHinter) {
        mPipDisplayLayoutState = pipDisplayLayoutState;
        mMainExecutor = mainExecutor;
        mSystemPerformanceHinter = systemPerformanceHinter;
    }

    /**
     * Starts a high perf session.
     *
     * @param timeoutCallback an optional callback to be executed upon session timeout.
     * @return a wrapper around the session to allow for early closing; null if no free sessions
     * left available in the pool.
     */
    @Nullable
    public PipHighPerfSession startSession(@Nullable Consumer<PipHighPerfSession> timeoutCallback,
            String reason) {
        if (PipHighPerfSession.getActiveSessionsCount() == SESSION_POOL_SIZE) {
            return null;
        }

        HighPerfSession highPerfSession = mSystemPerformanceHinter.startSession(HINT_SF,
                mPipDisplayLayoutState.getDisplayId(), "pip-high-perf-session");
        PipHighPerfSession pipHighPerfSession = new PipHighPerfSession(highPerfSession, reason);

        if (timeoutCallback != null) {
            mMainExecutor.executeDelayed(() -> {
                if (PipHighPerfSession.hasClosedOrFinalized(pipHighPerfSession)) {
                    // If the session is either directly closed or GC collected before timeout
                    // was reached, do not send the timeout callback.
                    return;
                }
                // The session hasn't been closed yet, so do that now, along with any cleanup.
                pipHighPerfSession.close();
                ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: high perf session %s timed out", TAG,
                        pipHighPerfSession.toString());
                timeoutCallback.accept(pipHighPerfSession);
            }, SESSION_TIMEOUT_DELAY);
        }
        ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: high perf session %s is started",
                TAG, pipHighPerfSession.toString());
        return pipHighPerfSession;
    }

    /**
     * Dumps the inner state.
     */
    public void dump(PrintWriter pw, String prefix) {
        final String innerPrefix = prefix + "  ";
        pw.println(prefix + TAG);
        pw.println(innerPrefix + "activeSessionCount="
                + PipHighPerfSession.getActiveSessionsCount());
    }

    /**
     * A wrapper around {@link HighPerfSession} to keep track of some extra metadata about
     * the session's status.
     */
    public class PipHighPerfSession implements AutoCloseable{

        // THe actual HighPerfSession we wrap around.
        private final HighPerfSession mSession;

        private final String mReason;

        /**
         * Keeps track of all active sessions using weakly referenced keys.
         * This makes sure that that sessions do not get accidentally leaked if not closed.
         */
        private static Map<PipHighPerfSession, Boolean> sActiveSessions = new WeakHashMap<>();

        private PipHighPerfSession(HighPerfSession session, String reason) {
            mSession = session;
            mReason = reason;
            sActiveSessions.put(this, true);
        }

        /**
         * Closes a high perf session.
         */
        @Override
        public void close() {
            sActiveSessions.remove(this);
            mSession.close();
            ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: high perf session %s is closed",
                    TAG, toString());
        }

        @Override
        public void finalize() {
            // The entry should be removed from the weak hash map as well by default.
            mSession.close();
        }

        @Override
        public String toString() {
            return "[" + super.toString() + "] initially started due to: " + mReason;
        }

        private static boolean hasClosedOrFinalized(PipHighPerfSession pipHighPerfSession) {
            return !sActiveSessions.containsKey(pipHighPerfSession);
        }

        private static int getActiveSessionsCount() {
            return sActiveSessions.size();
        }
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
@@ -394,6 +395,20 @@ public abstract class WMShellBaseModule {
        return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
    }

    @WMSingleton
    @Provides
    static Optional<PipPerfHintController> providePipPerfHintController(
            PipDisplayLayoutState pipDisplayLayoutState,
            @ShellMainThread ShellExecutor mainExecutor,
            Optional<SystemPerformanceHinter> systemPerformanceHinterOptional) {
        if (systemPerformanceHinterOptional.isPresent()) {
            return Optional.of(new PipPerfHintController(pipDisplayLayoutState, mainExecutor,
                    systemPerformanceHinterOptional.get()));
        } else {
            return Optional.empty();
        }
    }

    @WMSingleton
    @Provides
    static PipBoundsState providePipBoundsState(Context context,
+11 −6
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
@@ -143,10 +144,12 @@ public abstract class Pip1Module {
            PipMotionHelper pipMotionHelper,
            FloatingContentCoordinator floatingContentCoordinator,
            PipUiEventLogger pipUiEventLogger,
            @ShellMainThread ShellExecutor mainExecutor) {
            @ShellMainThread ShellExecutor mainExecutor,
            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
                pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
                floatingContentCoordinator, pipUiEventLogger, mainExecutor);
                floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                pipPerfHintControllerOptional);
    }

    @WMSingleton
@@ -169,6 +172,7 @@ public abstract class Pip1Module {
            PipTransitionController pipTransitionController,
            PipParamsChangedForwarder pipParamsChangedForwarder,
            Optional<SplitScreenController> splitScreenControllerOptional,
            Optional<PipPerfHintController> pipPerfHintControllerOptional,
            DisplayController displayController,
            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
            @ShellMainThread ShellExecutor mainExecutor) {
@@ -176,8 +180,8 @@ public abstract class Pip1Module {
                syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
                pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
                splitScreenControllerOptional, displayController, pipUiEventLogger,
                shellTaskOrganizer, mainExecutor);
                splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
                pipUiEventLogger, shellTaskOrganizer, mainExecutor);
    }

    @WMSingleton
@@ -209,10 +213,11 @@ public abstract class Pip1Module {
            PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
            PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
            PipTransitionController pipTransitionController,
            FloatingContentCoordinator floatingContentCoordinator) {
            FloatingContentCoordinator floatingContentCoordinator,
            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
                menuController, pipSnapAlgorithm, pipTransitionController,
                floatingContentCoordinator);
                floatingContentCoordinator, pipPerfHintControllerOptional);
    }

    @WMSingleton
+4 −2
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.dagger.WMShellBaseModule;
@@ -212,6 +213,7 @@ public abstract class TvPipModule {
            PipParamsChangedForwarder pipParamsChangedForwarder,
            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
            Optional<SplitScreenController> splitScreenControllerOptional,
            Optional<PipPerfHintController> pipPerfHintControllerOptional,
            DisplayController displayController,
            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
            @ShellMainThread ShellExecutor mainExecutor) {
@@ -219,8 +221,8 @@ public abstract class TvPipModule {
                syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
                tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
                pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
                splitScreenControllerOptional, displayController, pipUiEventLogger,
                shellTaskOrganizer, mainExecutor);
                splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
                pipUiEventLogger, shellTaskOrganizer, mainExecutor);
    }

    @WMSingleton
+30 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
@@ -140,6 +141,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
    private final int mCrossFadeAnimationDuration;
    private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
    private final Optional<SplitScreenController> mSplitScreenOptional;
    @Nullable private final PipPerfHintController mPipPerfHintController;
    protected final ShellTaskOrganizer mTaskOrganizer;
    protected final ShellExecutor mMainExecutor;

@@ -157,10 +159,30 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
    private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
            new PipAnimationController.PipAnimationCallback() {
                private boolean mIsCancelled;
                @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;

                private void onHighPerfSessionTimeout(
                        PipPerfHintController.PipHighPerfSession session) {}

                private void cleanUpHighPerfSessionMaybe() {
                    if (mPipHighPerfSession != null) {
                        // Close the high perf session once pointer interactions are over;
                        mPipHighPerfSession.close();
                        mPipHighPerfSession = null;
                    }
                }


                @Override
                public void onPipAnimationStart(TaskInfo taskInfo,
                        PipAnimationController.PipTransitionAnimator animator) {
                    if (mPipPerfHintController != null) {
                        // Start a high perf session with a timeout callback.
                        mPipHighPerfSession = mPipPerfHintController.startSession(
                                this::onHighPerfSessionTimeout,
                                "PipTaskOrganizer::mPipAnimationCallback");
                    }

                    final int direction = animator.getTransitionDirection();
                    mIsCancelled = false;
                    sendOnPipTransitionStarted(direction);
@@ -169,6 +191,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
                @Override
                public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
                        PipAnimationController.PipTransitionAnimator animator) {
                    // Close the high perf session if needed.
                    cleanUpHighPerfSessionMaybe();

                    final int direction = animator.getTransitionDirection();
                    if (mIsCancelled) {
                        sendOnPipTransitionFinished(direction);
@@ -356,6 +381,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
            @NonNull PipTransitionController pipTransitionController,
            @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
            Optional<SplitScreenController> splitScreenOptional,
            Optional<PipPerfHintController> pipPerfHintControllerOptional,
            @NonNull DisplayController displayController,
            @NonNull PipUiEventLogger pipUiEventLogger,
            @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -381,6 +407,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        mSurfaceControlTransactionFactory =
                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
        mSplitScreenOptional = splitScreenOptional;
        mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
        mTaskOrganizer = shellTaskOrganizer;
        mMainExecutor = mainExecutor;

@@ -1972,6 +1999,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
        pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
        mPipTransitionController.dump(pw, innerPrefix);
        if (mPipPerfHintController != null) {
            mPipPerfHintController.dump(pw, innerPrefix);
        }
    }

    @Override
Loading