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

Commit f99159c0 authored by Kazuki Takise's avatar Kazuki Takise
Browse files

Add task focus change support to FocusTransitionListener

This change adds task focus change support to FocusTransitionListener
to replace TaskInfo#isFocused with system-wide focus state.

The ideal, long-term solution is to implement this with a unified task
repository in WMShell, but as the project isn't ready yet, this change
workarounds some pain points that come from the compatibility with
existing APIs.

With this change, instead of FocusTransitionObserver registering as
TransitionObserver, focus state in FocusTransitionObserver is updated
in FreeformTransitionObserver#onTransitionReady(). This is because
focus state needs to be updated before FreeformTransitionObserver
processes the transition. Having one observer should be simpler
than having a order requirement between two observers, and this is
effectively merging FocusTransitionObserver and
FreeformTransitionObserver from the perspective of TransitionObserver,
but keeps these classes seperately to both avoid circular dependency
and isolate responsibilities. (Now FocusTransitionObserver no
longer implements TransitionObserver.)

Bug: 371143601
Flag: com.android.window.flags.enable_display_focus_in_shell_transitions
Test: atest FocusTransitionObserverTest
Change-Id: I68b6aaa44cf8578d3dcac3277c66e3d07738eef8
parent 173f6d13
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -26,5 +26,11 @@ public interface FocusTransitionListener {
    /**
     * Called when a transition changes the top, focused display.
     */
    void onFocusedDisplayChanged(int displayId);
    default void onFocusedDisplayChanged(int displayId) {}

    /**
     * Called when the per-app or system-wide focus state has changed for a task.
     */
    default void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
            boolean isFocusedGlobally) {}
}
+8 −6
Original line number Diff line number Diff line
@@ -111,6 +111,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.MixedTransitionHandler;
import com.android.wm.shell.transition.Transitions;
@@ -134,14 +135,14 @@ import dagger.Lazy;
import dagger.Module;
import dagger.Provides;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.MainCoroutineDispatcher;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
 * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
@@ -391,10 +392,11 @@ public abstract class WMShellModule {
            Transitions transitions,
            Optional<DesktopFullImmersiveTransitionHandler> desktopImmersiveTransitionHandler,
            WindowDecorViewModel windowDecorViewModel,
            Optional<TaskChangeListener> taskChangeListener) {
            Optional<TaskChangeListener> taskChangeListener,
            FocusTransitionObserver focusTransitionObserver) {
        return new FreeformTaskTransitionObserver(
                context, shellInit, transitions, desktopImmersiveTransitionHandler,
                windowDecorViewModel, taskChangeListener);
                windowDecorViewModel, taskChangeListener, focusTransitionObserver);
    }

    @WMSingleton
+8 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.window.flags.Flags;
import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;

@@ -50,6 +51,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
    private final Optional<DesktopFullImmersiveTransitionHandler> mImmersiveTransitionHandler;
    private final WindowDecorViewModel mWindowDecorViewModel;
    private final Optional<TaskChangeListener> mTaskChangeListener;
    private final FocusTransitionObserver mFocusTransitionObserver;

    private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
            new HashMap<>();
@@ -60,11 +62,13 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
            Transitions transitions,
            Optional<DesktopFullImmersiveTransitionHandler> immersiveTransitionHandler,
            WindowDecorViewModel windowDecorViewModel,
            Optional<TaskChangeListener> taskChangeListener) {
            Optional<TaskChangeListener> taskChangeListener,
            FocusTransitionObserver focusTransitionObserver) {
        mTransitions = transitions;
        mImmersiveTransitionHandler = immersiveTransitionHandler;
        mWindowDecorViewModel = windowDecorViewModel;
        mTaskChangeListener = taskChangeListener;
        mFocusTransitionObserver = focusTransitionObserver;
        if (FreeformComponents.isFreeformEnabled(context)) {
            shellInit.addInitCallback(this::onInit, this);
        }
@@ -87,6 +91,9 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
            //  Otherwise window decoration relayout won't run with the immersive state up to date.
            mImmersiveTransitionHandler.ifPresent(h -> h.onTransitionReady(transition));
        }
        // Update focus state first to ensure the correct state can be queried from listeners.
        // TODO(371503964): Remove this once the unified task repository is ready.
        mFocusTransitionObserver.updateFocusState(info);

        final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
        final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
+77 −26
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@

package com.android.wm.shell.transition;

import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;

@@ -24,10 +25,11 @@ import static com.android.window.flags.Flags.enableDisplayFocusInShellTransition
import static com.android.wm.shell.transition.Transitions.TransitionObserver;

import android.annotation.NonNull;
import android.os.IBinder;
import android.app.ActivityManager.RunningTaskInfo;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Slog;
import android.view.SurfaceControl;
import android.util.SparseArray;
import android.window.TransitionInfo;

import com.android.wm.shell.shared.FocusTransitionListener;
@@ -43,43 +45,63 @@ import java.util.concurrent.Executor;
 * It reports transitions to callers outside of the process via {@link IFocusTransitionListener},
 * and callers within the process via {@link FocusTransitionListener}.
 */
public class FocusTransitionObserver implements TransitionObserver {
public class FocusTransitionObserver {
    private static final String TAG = FocusTransitionObserver.class.getSimpleName();

    private IFocusTransitionListener mRemoteListener;
    private final Map<FocusTransitionListener, Executor> mLocalListeners =
            new HashMap<>();

    private int mFocusedDisplayId = INVALID_DISPLAY;
    private int mFocusedDisplayId = DEFAULT_DISPLAY;
    private final SparseArray<RunningTaskInfo> mFocusedTaskOnDisplay = new SparseArray<>();

    private final ArraySet<RunningTaskInfo> mTmpTasksToBeNotified = new ArraySet<>();

    public FocusTransitionObserver() {}

    @Override
    public void onTransitionReady(@NonNull IBinder transition,
            @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction) {
    /**
     * Update display/window focus state from the given transition info and notifies changes if any.
     */
    public void updateFocusState(@NonNull TransitionInfo info) {
        if (!enableDisplayFocusInShellTransitions()) {
            return;
        }
        final List<TransitionInfo.Change> changes = info.getChanges();
        for (int i = changes.size() - 1; i >= 0; i--) {
            final TransitionInfo.Change change = changes.get(i);

            final RunningTaskInfo task = change.getTaskInfo();
            if (task != null
                    && (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN)) {
                final RunningTaskInfo lastFocusedTaskOnDisplay =
                        mFocusedTaskOnDisplay.get(task.displayId);
                if (lastFocusedTaskOnDisplay != null) {
                    mTmpTasksToBeNotified.add(lastFocusedTaskOnDisplay);
                }
                mTmpTasksToBeNotified.add(task);
                mFocusedTaskOnDisplay.put(task.displayId, task);
            }

            if (change.hasFlags(FLAG_IS_DISPLAY) && change.hasFlags(FLAG_MOVED_TO_TOP)) {
                if (mFocusedDisplayId != change.getEndDisplayId()) {
                    final RunningTaskInfo lastGloballyFocusedTask =
                            mFocusedTaskOnDisplay.get(mFocusedDisplayId);
                    if (lastGloballyFocusedTask != null) {
                        mTmpTasksToBeNotified.add(lastGloballyFocusedTask);
                    }
                    mFocusedDisplayId = change.getEndDisplayId();
                    notifyFocusedDisplayChanged();
                    final RunningTaskInfo currentGloballyFocusedTask =
                            mFocusedTaskOnDisplay.get(mFocusedDisplayId);
                    if (currentGloballyFocusedTask != null) {
                        mTmpTasksToBeNotified.add(currentGloballyFocusedTask);
                    }
                return;
                }
            }
        }

    @Override
    public void onTransitionStarting(@NonNull IBinder transition) {}

    @Override
    public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}

    @Override
    public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
        mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged);
        mTmpTasksToBeNotified.clear();
    }

    /**
     * Sets the focus transition listener that receives any transitions resulting in focus switch.
@@ -92,7 +114,10 @@ public class FocusTransitionObserver implements TransitionObserver {
            return;
        }
        mLocalListeners.put(listener, executor);
        executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId));
        executor.execute(() -> {
            listener.onFocusedDisplayChanged(mFocusedDisplayId);
            mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged);
        });
    }

    /**
@@ -120,13 +145,20 @@ public class FocusTransitionObserver implements TransitionObserver {
        notifyFocusedDisplayChangedToRemote();
    }

    /**
     * Notifies the listener that display focus has changed.
     */
    public void notifyFocusedDisplayChanged() {
    private void notifyTaskFocusChanged(RunningTaskInfo task) {
        final boolean isFocusedOnDisplay = isFocusedOnDisplay(task);
        final boolean isFocusedGlobally = hasGlobalFocus(task);
        mLocalListeners.forEach((listener, executor) ->
                executor.execute(() -> listener.onFocusedTaskChanged(task.taskId,
                        isFocusedOnDisplay, isFocusedGlobally)));
    }

    private void notifyFocusedDisplayChanged() {
        notifyFocusedDisplayChangedToRemote();
        mLocalListeners.forEach((listener, executor) ->
                executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId)));
                executor.execute(() -> {
                    listener.onFocusedDisplayChanged(mFocusedDisplayId);
                }));
    }

    private void notifyFocusedDisplayChangedToRemote() {
@@ -138,4 +170,23 @@ public class FocusTransitionObserver implements TransitionObserver {
            }
        }
    }

    private boolean isFocusedOnDisplay(@NonNull RunningTaskInfo task) {
        if (!enableDisplayFocusInShellTransitions()) {
            return task.isFocused;
        }
        final RunningTaskInfo focusedTaskOnDisplay = mFocusedTaskOnDisplay.get(task.displayId);
        return focusedTaskOnDisplay != null && focusedTaskOnDisplay.taskId == task.taskId;
    }

    /**
     * Checks whether the given task has focused globally on the system.
     * (Note {@link RunningTaskInfo#isFocused} represents per-display focus.)
     */
    public boolean hasGlobalFocus(@NonNull RunningTaskInfo task) {
        if (!enableDisplayFocusInShellTransitions()) {
            return task.isFocused;
        }
        return task.displayId == mFocusedDisplayId && isFocusedOnDisplay(task);
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -392,8 +392,6 @@ public class Transitions implements RemoteCallable<Transitions>,

        mShellCommandHandler.addCommandCallback("transitions", this, this);
        mShellCommandHandler.addDumpCallback(this::dump, this);

        registerObserver(mFocusTransitionObserver);
    }

    public boolean isRegistered() {
Loading