summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java30
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl28
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java142
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java12
11 files changed, 435 insertions, 12 deletions
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
new file mode 100644
index 000000000000..26aae2d2aa78
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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.shared;
+
+import com.android.wm.shell.shared.annotations.ExternalThread;
+
+/**
+ * Listener to get focus-related transition callbacks.
+ */
+@ExternalThread
+public interface FocusTransitionListener {
+ /**
+ * Called when a transition changes the top, focused display.
+ */
+ void onFocusedDisplayChanged(int displayId);
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl
new file mode 100644
index 000000000000..b91d5b6e2769
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.shared;
+
+/**
+ * Listener interface that to get focus-related transition callbacks.
+ */
+oneway interface IFocusTransitionListener {
+
+ /**
+ * Called when a transition changes the top, focused display.
+ */
+ void onFocusedDisplayChanged(int displayId);
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
index 3256abf09116..02615a96a86c 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
@@ -20,6 +20,7 @@ import android.view.SurfaceControl;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
+import com.android.wm.shell.shared.IFocusTransitionListener;
import com.android.wm.shell.shared.IHomeTransitionListener;
/**
@@ -59,4 +60,9 @@ interface IShellTransitions {
*/
oneway void registerRemoteForTakeover(in TransitionFilter filter,
in RemoteTransition remoteTransition) = 6;
+
+ /**
+ * Set listener that will receive callbacks about transitions involving focus switch.
+ */
+ oneway void setFocusTransitionListener(in IFocusTransitionListener listener) = 7;
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
index 6d4ab4c1bd09..2db4311fb771 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
@@ -22,6 +22,8 @@ import android.window.TransitionFilter;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import java.util.concurrent.Executor;
+
/**
* Interface to manage remote transitions.
*/
@@ -44,4 +46,15 @@ public interface ShellTransitions {
* Unregisters a remote transition for all operations.
*/
default void unregisterRemote(@NonNull RemoteTransition remoteTransition) {}
+
+ /**
+ * Sets listener that will receive callbacks about transitions involving focus switch.
+ */
+ default void setFocusTransitionListener(@NonNull FocusTransitionListener listener,
+ Executor executor) {}
+
+ /**
+ * Unsets listener that will receive callbacks about transitions involving focus switch.
+ */
+ default void unsetFocusTransitionListener(@NonNull FocusTransitionListener listener) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index bec2ea58e106..4227a6e2903f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -123,6 +123,7 @@ import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
+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;
@@ -742,14 +743,15 @@ public abstract class WMShellBaseModule {
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- HomeTransitionObserver homeTransitionObserver) {
+ HomeTransitionObserver homeTransitionObserver,
+ FocusTransitionObserver focusTransitionObserver) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
pool, displayController, mainExecutor, mainHandler, animExecutor,
- rootTaskDisplayAreaOrganizer, homeTransitionObserver);
+ rootTaskDisplayAreaOrganizer, homeTransitionObserver, focusTransitionObserver);
}
@WMSingleton
@@ -761,6 +763,12 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static FocusTransitionObserver provideFocusTransitionObserver() {
+ return new FocusTransitionObserver();
+ }
+
+ @WMSingleton
+ @Provides
static TaskViewTransitions provideTaskViewTransitions(Transitions transitions) {
return new TaskViewTransitions(transitions);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
new file mode 100644
index 000000000000..2f5059f3161c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
@@ -0,0 +1,142 @@
+/*
+ * 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.transition;
+
+import static android.view.Display.INVALID_DISPLAY;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+import static com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.shared.FocusTransitionListener;
+import com.android.wm.shell.shared.IFocusTransitionListener;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving focus switch.
+ * 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 {
+ private static final String TAG = FocusTransitionObserver.class.getSimpleName();
+
+ private IFocusTransitionListener mRemoteListener;
+ private final Map<FocusTransitionListener, Executor> mLocalListeners =
+ new HashMap<>();
+
+ private int mFocusedDisplayId = INVALID_DISPLAY;
+
+ public FocusTransitionObserver() {}
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ 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 && task.isFocused && change.hasFlags(FLAG_MOVED_TO_TOP)) {
+ if (mFocusedDisplayId != task.displayId) {
+ mFocusedDisplayId = task.displayId;
+ notifyFocusedDisplayChanged();
+ }
+ 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) {}
+
+ /**
+ * Sets the focus transition listener that receives any transitions resulting in focus switch.
+ * This is for calls from outside the Shell, within the host process.
+ *
+ */
+ public void setLocalFocusTransitionListener(FocusTransitionListener listener,
+ Executor executor) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
+ mLocalListeners.put(listener, executor);
+ executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId));
+ }
+
+ /**
+ * Sets the focus transition listener that receives any transitions resulting in focus switch.
+ * This is for calls from outside the Shell, within the host process.
+ *
+ */
+ public void unsetLocalFocusTransitionListener(FocusTransitionListener listener) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
+ mLocalListeners.remove(listener);
+ }
+
+ /**
+ * Sets the focus transition listener that receives any transitions resulting in focus switch.
+ * This is for calls from outside the host process.
+ */
+ public void setRemoteFocusTransitionListener(Transitions transitions,
+ IFocusTransitionListener listener) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
+ mRemoteListener = listener;
+ notifyFocusedDisplayChangedToRemote();
+ }
+
+ /**
+ * Notifies the listener that display focus has changed.
+ */
+ public void notifyFocusedDisplayChanged() {
+ notifyFocusedDisplayChangedToRemote();
+ mLocalListeners.forEach((listener, executor) ->
+ executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId)));
+ }
+
+ private void notifyFocusedDisplayChangedToRemote() {
+ if (mRemoteListener != null) {
+ try {
+ mRemoteListener.onFocusedDisplayChanged(mFocusedDisplayId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call notifyFocusedDisplayChangedToRemote", e);
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index d03832d3e85e..d280dcd252b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -87,6 +87,8 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.FocusTransitionListener;
+import com.android.wm.shell.shared.IFocusTransitionListener;
import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.shared.ShellTransitions;
@@ -103,6 +105,7 @@ import com.android.wm.shell.transition.tracing.TransitionTracer;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.Executor;
/**
* Plays transition animations. Within this player, each transition has a lifecycle.
@@ -224,6 +227,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
private HomeTransitionObserver mHomeTransitionObserver;
+ private FocusTransitionObserver mFocusTransitionObserver;
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -309,10 +313,12 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @NonNull HomeTransitionObserver observer) {
+ @NonNull HomeTransitionObserver homeTransitionObserver,
+ @NonNull FocusTransitionObserver focusTransitionObserver) {
this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit), observer);
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit),
+ homeTransitionObserver, focusTransitionObserver);
}
public Transitions(@NonNull Context context,
@@ -326,7 +332,8 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
@NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- @NonNull HomeTransitionObserver observer) {
+ @NonNull HomeTransitionObserver homeTransitionObserver,
+ @NonNull FocusTransitionObserver focusTransitionObserver) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -345,7 +352,8 @@ public class Transitions implements RemoteCallable<Transitions>,
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
- mHomeTransitionObserver = observer;
+ mHomeTransitionObserver = homeTransitionObserver;
+ mFocusTransitionObserver = focusTransitionObserver;
if (android.tracing.Flags.perfettoTransitionTracing()) {
mTransitionTracer = new PerfettoTransitionTracer();
@@ -384,6 +392,8 @@ public class Transitions implements RemoteCallable<Transitions>,
mShellCommandHandler.addCommandCallback("transitions", this, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+
+ registerObserver(mFocusTransitionObserver);
}
public boolean isRegistered() {
@@ -1573,6 +1583,21 @@ public class Transitions implements RemoteCallable<Transitions>,
mMainExecutor.execute(
() -> mRemoteTransitionHandler.removeFiltered(remoteTransition));
}
+
+ @Override
+ public void setFocusTransitionListener(FocusTransitionListener listener,
+ Executor executor) {
+ mMainExecutor.execute(() ->
+ mFocusTransitionObserver.setLocalFocusTransitionListener(listener, executor));
+
+ }
+
+ @Override
+ public void unsetFocusTransitionListener(FocusTransitionListener listener) {
+ mMainExecutor.execute(() ->
+ mFocusTransitionObserver.unsetLocalFocusTransitionListener(listener));
+
+ }
}
/**
@@ -1634,6 +1659,15 @@ public class Transitions implements RemoteCallable<Transitions>,
}
@Override
+ public void setFocusTransitionListener(IFocusTransitionListener listener) {
+ executeRemoteCallWithTaskPermission(mTransitions, "setFocusTransitionListener",
+ (transitions) -> {
+ transitions.mFocusTransitionObserver.setRemoteFocusTransitionListener(
+ transitions, listener);
+ });
+ }
+
+ @Override
public SurfaceControl getHomeTaskOverlayContainer() {
SurfaceControl[] result = new SurfaceControl[1];
executeRemoteCallWithTaskPermission(mTransitions, "getHomeTaskOverlayContainer",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a6c16c43c8cb..67eda8bfecd1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -74,6 +74,7 @@ import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
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.Transitions;
@@ -429,7 +430,8 @@ public class StageCoordinatorTests extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
new file mode 100644
index 000000000000..d37b4cf4b4b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.transition;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionInfo.TransitionMode;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.window.flags.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.IFocusTransitionListener;
+import com.android.wm.shell.shared.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the focus transition observer.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+public class FocusTransitionObserverTest extends ShellTestCase {
+
+ static final int SECONDARY_DISPLAY_ID = 1;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ private IFocusTransitionListener mListener;
+ private Transitions mTransition;
+ private FocusTransitionObserver mFocusTransitionObserver;
+
+ @Before
+ public void setUp() {
+ mListener = mock(IFocusTransitionListener.class);
+ when(mListener.asBinder()).thenReturn(mock(IBinder.class));
+
+ mFocusTransitionObserver = new FocusTransitionObserver();
+ mTransition =
+ new Transitions(InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ mock(ShellInit.class), mock(ShellController.class),
+ mock(ShellTaskOrganizer.class), mock(TransactionPool.class),
+ mock(DisplayController.class), new TestShellExecutor(),
+ new Handler(Looper.getMainLooper()), new TestShellExecutor(),
+ mock(HomeTransitionObserver.class),
+ mFocusTransitionObserver);
+ mFocusTransitionObserver.setRemoteFocusTransitionListener(mTransition, mListener);
+ }
+
+ @Test
+ public void testTransitionWithMovedToFrontFlagChangesDisplayFocus() throws RemoteException {
+ final IBinder binder = mock(IBinder.class);
+ final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+
+ // Open a task on the default display, which doesn't change display focus because the
+ // default display already has it.
+ TransitionInfo info = mock(TransitionInfo.class);
+ final List<TransitionInfo.Change> changes = new ArrayList<>();
+ setupChange(changes, 123 /* taskId */, TRANSIT_OPEN, DEFAULT_DISPLAY,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, never()).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ clearInvocations(mListener);
+
+ // Open a new task on the secondary display and verify display focus changes to the display.
+ changes.clear();
+ setupChange(changes, 456 /* taskId */, TRANSIT_OPEN, SECONDARY_DISPLAY_ID,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, times(1)).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ clearInvocations(mListener);
+
+ // Open the first task to front and verify display focus goes back to the default display.
+ changes.clear();
+ setupChange(changes, 123 /* taskId */, TRANSIT_TO_FRONT, DEFAULT_DISPLAY,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, times(1)).onFocusedDisplayChanged(DEFAULT_DISPLAY);
+ clearInvocations(mListener);
+
+ // Open another task on the default display and verify no display focus switch as it's
+ // already on the default display.
+ changes.clear();
+ setupChange(changes, 789 /* taskId */, TRANSIT_OPEN, DEFAULT_DISPLAY,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, never()).onFocusedDisplayChanged(DEFAULT_DISPLAY);
+ }
+
+ private void setupChange(List<TransitionInfo.Change> changes, int taskId,
+ @TransitionMode int mode, int displayId, boolean focused) {
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ RunningTaskInfo taskInfo = mock(RunningTaskInfo.class);
+ taskInfo.taskId = taskId;
+ taskInfo.isFocused = focused;
+ when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(focused);
+ taskInfo.displayId = displayId;
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(change.getMode()).thenReturn(mode);
+ changes.add(change);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 8f49de0a98fb..8dfdfb4dcbcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -100,7 +100,8 @@ public class HomeTransitionObserverTest extends ShellTestCase {
mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor);
mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
mOrganizer, mTransactionPool, mDisplayController, mMainExecutor,
- mMainHandler, mAnimExecutor, mHomeTransitionObserver);
+ mMainHandler, mAnimExecutor, mHomeTransitionObserver,
+ mock(FocusTransitionObserver.class));
mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index aea14b900647..6cde0569796d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -158,7 +158,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellInit shellInit = mock(ShellInit.class);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
// One from Transitions, one from RootTaskDisplayAreaOrganizer
verify(shellInit).addInitCallback(any(), eq(t));
verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
@@ -170,7 +171,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellController shellController = mock(ShellController.class);
final Transitions t = new Transitions(mContext, shellInit, shellController,
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
@@ -1238,7 +1240,8 @@ public class ShellTransitionTests extends ShellTestCase {
final Transitions transitions =
new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
@@ -1780,7 +1783,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
shellInit.init();
return t;
}