summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/OWNERS2
-rw-r--r--libs/WindowManager/Shell/Android.bp4
-rw-r--r--libs/WindowManager/Shell/res/layout/split_divider.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java125
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java240
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java171
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java205
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java102
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java)68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java155
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java)5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java123
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java236
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java102
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java)163
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java98
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt114
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt95
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt104
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java106
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java10
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/FrameInfo.cpp6
-rw-r--r--libs/hwui/FrameInfo.h3
-rw-r--r--libs/hwui/Layer.cpp86
-rw-r--r--libs/hwui/Layer.h3
-rw-r--r--libs/hwui/apex/jni_runtime.cpp2
-rw-r--r--libs/hwui/canvas/CanvasOpRasterizer.cpp6
-rw-r--r--libs/hwui/canvas/CanvasOpTypes.h3
-rw-r--r--libs/hwui/canvas/CanvasOps.h39
-rw-r--r--libs/hwui/canvas/Points.h48
-rw-r--r--libs/hwui/jni/fonts/Font.cpp35
-rw-r--r--libs/hwui/jni/fonts/NativeFont.cpp125
-rw-r--r--libs/hwui/tests/unit/CanvasOpTests.cpp49
-rw-r--r--libs/incident/OWNERS1
-rw-r--r--libs/input/OWNERS1
-rw-r--r--libs/storage/OWNERS1
-rw-r--r--libs/usb/OWNERS1
96 files changed, 2775 insertions, 1388 deletions
diff --git a/libs/WindowManager/OWNERS b/libs/WindowManager/OWNERS
index 063d4594f2fa..2c61df96eb03 100644
--- a/libs/WindowManager/OWNERS
+++ b/libs/WindowManager/OWNERS
@@ -1,3 +1,3 @@
set noparent
-include ../../services/core/java/com/android/server/wm/OWNERS \ No newline at end of file
+include /services/core/java/com/android/server/wm/OWNERS
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 39e32c694d2e..96e0559b0df6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -122,12 +122,14 @@ android_library {
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"iconloader_base",
+ "jsr330",
"protolog-lib",
"SettingsLib",
"WindowManager-Shell-proto",
+ "jsr330"
],
kotlincflags: ["-Xjvm-default=enable"],
manifest: "AndroidManifest.xml",
min_sdk_version: "26",
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index b86f36a14d17..341fe617b2d0 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.wm.shell.apppairs.DividerView
+<com.android.wm.shell.common.split.DividerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
@@ -24,4 +24,4 @@
android:id="@+id/docked_divider_background"
android:background="@color/docked_divider_background"/>
-</com.android.wm.shell.apppairs.DividerView>
+</com.android.wm.shell.common.split.DividerView>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index ada7e1a9e3ab..45948dd9e800 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -19,6 +19,7 @@ package com.android.wm.shell;
import android.view.Gravity;
import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.letterbox.LetterboxConfigController;
import com.android.wm.shell.onehanded.OneHanded;
@@ -61,6 +62,7 @@ public final class ShellCommandHandler {
}
/** Dumps WM Shell internal state. */
+ @ExternalThread
public void dump(PrintWriter pw) {
mShellTaskOrganizer.dump(pw, "");
pw.println();
@@ -76,6 +78,7 @@ public final class ShellCommandHandler {
/** Returns {@code true} if command was found and executed. */
+ @ExternalThread
public boolean handleCommand(String[] args, PrintWriter pw) {
if (args.length < 2) {
// Argument at position 0 is "WMShell".
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
index 118f189866eb..94555de4f05c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERB
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.letterbox.LetterboxTaskListener;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -56,6 +57,7 @@ public class ShellInit {
mFullscreenTaskListener = fullscreenTaskListener;
}
+ @ExternalThread
public void init() {
// Start listening for display changes
mDisplayImeController.startMonitorDisplays();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index beb9690b5cbc..174c16afc75f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -31,12 +31,14 @@ import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
+import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -45,6 +47,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
@@ -119,7 +122,7 @@ public class ShellTaskOrganizer extends TaskOrganizer {
ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) {
super(taskOrganizerController, mainExecutor);
mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) mTransitions.register(this);
// TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
// by a controller, that class should be create while porting
// ActivityRecord#addStartingWindow to WMShell.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
index 120039de1240..10195b6a26b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
@@ -23,9 +23,9 @@ import static android.window.TransitionInfo.TRANSIT_SHOW;
import android.animation.Animator;
import android.animation.ValueAnimator;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
@@ -35,15 +35,18 @@ import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
import android.window.WindowOrganizer;
+import androidx.annotation.BinderThread;
+
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
/** Plays transition animations */
-public class Transitions extends ITransitionPlayer.Stub {
+public class Transitions {
private static final String TAG = "ShellTransitions";
/** Set to {@code true} to enable shell transitions. */
@@ -54,6 +57,7 @@ public class Transitions extends ITransitionPlayer.Stub {
private final TransactionPool mTransactionPool;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
+ private final TransitionPlayerImpl mPlayerImpl;
/** Keeps track of currently tracked transitions and all the animations associated with each */
private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
@@ -64,6 +68,11 @@ public class Transitions extends ITransitionPlayer.Stub {
mTransactionPool = pool;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mPlayerImpl = new TransitionPlayerImpl();
+ }
+
+ public void register(ShellTaskOrganizer taskOrganizer) {
+ taskOrganizer.registerTransitionPlayer(mPlayerImpl);
}
// TODO(shell-transitions): real animations
@@ -115,77 +124,73 @@ public class Transitions extends ITransitionPlayer.Stub {
|| type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
}
- @Override
- public void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
+ private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
// start task
- mMainExecutor.execute(() -> {
- if (!mActiveTransitions.containsKey(transitionToken)) {
- Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
- + " expecting one of " + mActiveTransitions.keySet());
- }
- if (mActiveTransitions.get(transitionToken) != null) {
- throw new IllegalStateException("Got a duplicate onTransitionReady call for "
- + transitionToken);
- }
- mActiveTransitions.put(transitionToken, new ArrayList<>());
- boolean isOpening = isOpeningType(info.getType());
- if (info.getRootLeash().isValid()) {
- t.show(info.getRootLeash());
- }
- // changes should be ordered top-to-bottom in z
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
-
- // Don't animate anything with an animating parent
- if (change.getParent() != null) {
- if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
- t.show(leash);
- t.setMatrix(leash, 1, 0, 0, 1);
- }
- continue;
- }
-
- t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x,
- change.getEndAbsBounds().top - info.getRootOffset().y);
- // Put all the OPEN/SHOW on top
+ if (!mActiveTransitions.containsKey(transitionToken)) {
+ Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
+ + " expecting one of " + mActiveTransitions.keySet());
+ }
+ if (mActiveTransitions.get(transitionToken) != null) {
+ throw new IllegalStateException("Got a duplicate onTransitionReady call for "
+ + transitionToken);
+ }
+ mActiveTransitions.put(transitionToken, new ArrayList<>());
+ boolean isOpening = isOpeningType(info.getType());
+ if (info.getRootLeash().isValid()) {
+ t.show(info.getRootLeash());
+ }
+ // changes should be ordered top-to-bottom in z
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ // Don't animate anything with an animating parent
+ if (change.getParent() != null) {
if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
t.show(leash);
t.setMatrix(leash, 1, 0, 0, 1);
- if (isOpening) {
- // put on top and fade in
- t.setLayer(leash, info.getChanges().size() - i);
- t.setAlpha(leash, 0.f);
- startExampleAnimation(transitionToken, leash, true /* show */);
- } else {
- // put on bottom and leave it visible without fade
- t.setLayer(leash, -i);
- t.setAlpha(leash, 1.f);
- }
- } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) {
- if (isOpening) {
- // put on bottom and leave visible without fade
- t.setLayer(leash, -i);
- } else {
- // put on top and fade out
- t.setLayer(leash, info.getChanges().size() - i);
- startExampleAnimation(transitionToken, leash, false /* show */);
- }
+ }
+ continue;
+ }
+
+ t.reparent(leash, info.getRootLeash());
+ t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x,
+ change.getEndAbsBounds().top - info.getRootOffset().y);
+ // Put all the OPEN/SHOW on top
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
+ t.show(leash);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ if (isOpening) {
+ // put on top and fade in
+ t.setLayer(leash, info.getChanges().size() - i);
+ t.setAlpha(leash, 0.f);
+ startExampleAnimation(transitionToken, leash, true /* show */);
} else {
+ // put on bottom and leave it visible without fade
+ t.setLayer(leash, -i);
+ t.setAlpha(leash, 1.f);
+ }
+ } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) {
+ if (isOpening) {
+ // put on bottom and leave visible without fade
+ t.setLayer(leash, -i);
+ } else {
+ // put on top and fade out
t.setLayer(leash, info.getChanges().size() - i);
+ startExampleAnimation(transitionToken, leash, false /* show */);
}
+ } else {
+ t.setLayer(leash, info.getChanges().size() - i);
}
- t.apply();
- onFinish(transitionToken);
- });
+ }
+ t.apply();
+ onFinish(transitionToken);
}
- @MainThread
private void onFinish(IBinder transition) {
if (!mActiveTransitions.get(transition).isEmpty()) return;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -194,16 +199,32 @@ public class Transitions extends ITransitionPlayer.Stub {
mOrganizer.finishTransition(transition, null, null);
}
- @Override
- public void requestStartTransition(int type, @NonNull IBinder transitionToken) {
+ private void requestStartTransition(int type, @NonNull IBinder transitionToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
type, transitionToken);
- mMainExecutor.execute(() -> {
- if (mActiveTransitions.containsKey(transitionToken)) {
- throw new RuntimeException("Transition already started " + transitionToken);
- }
- IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
- mActiveTransitions.put(transition, null);
- });
+
+ if (mActiveTransitions.containsKey(transitionToken)) {
+ throw new RuntimeException("Transition already started " + transitionToken);
+ }
+ IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
+ mActiveTransitions.put(transition, null);
+ }
+
+ @BinderThread
+ private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
+ @Override
+ public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
+ SurfaceControl.Transaction transaction) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ Transitions.this.onTransitionReady(iBinder, transitionInfo, transaction);
+ });
+ }
+
+ @Override
+ public void requestStartTransition(int i, IBinder iBinder) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ Transitions.this.requestStartTransition(i, iBinder);
+ });
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index acb9a5dae78c..834de3f15b1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -18,11 +18,11 @@ package com.android.wm.shell;
import static android.view.Display.DEFAULT_DISPLAY;
-import android.app.WindowConfiguration;
import android.os.RemoteException;
-import android.view.WindowManagerGlobal;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedStackListener;
/**
* The singleton wrapper to communicate between WindowManagerService and WMShell features
@@ -31,32 +31,30 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder;
public class WindowManagerShellWrapper {
private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
- public static final int WINDOWING_MODE_PINNED = WindowConfiguration.WINDOWING_MODE_PINNED;
-
/**
* Forwarder to which we can add multiple pinned stack listeners. Each listener will receive
* updates from the window manager service.
*/
- private PinnedStackListenerForwarder mPinnedStackListenerForwarder =
- new PinnedStackListenerForwarder();
+ private final PinnedStackListenerForwarder mPinnedStackListenerForwarder;
+
+ public WindowManagerShellWrapper(ShellExecutor shellMainExecutor) {
+ mPinnedStackListenerForwarder = new PinnedStackListenerForwarder(shellMainExecutor);
+ }
/**
* Adds a pinned stack listener, which will receive updates from the window manager service
* along with any other pinned stack listeners that were added via this method.
*/
- public void addPinnedStackListener(PinnedStackListenerForwarder.PinnedStackListener listener)
- throws
- RemoteException {
+ public void addPinnedStackListener(PinnedStackListener listener)
+ throws RemoteException {
mPinnedStackListenerForwarder.addListener(listener);
- WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
- DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
+ mPinnedStackListenerForwarder.register(DEFAULT_DISPLAY);
}
/**
* Removes a pinned stack listener.
*/
- public void removePinnedStackListener(
- PinnedStackListenerForwarder.PinnedStackListener listener) {
+ public void removePinnedStackListener(PinnedStackListener listener) {
mPinnedStackListenerForwarder.removeListener(listener);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
index 357f777e1270..176c620fa119 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
@@ -23,6 +23,8 @@ import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import javax.inject.Inject;
+
/**
* Utility class to calculate general fling animation when the finger is released.
*/
@@ -368,6 +370,7 @@ public class FlingAnimationUtils {
float mX2;
float mY2;
+ @Inject
public Builder(DisplayMetrics displayMetrics) {
mDisplayMetrics = displayMetrics;
reset();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index f199072f7bca..cfbf8452ddae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -29,11 +29,13 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitLayout;
import java.io.PrintWriter;
@@ -42,7 +44,7 @@ import java.io.PrintWriter;
* {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
* Also includes all UI for managing the pair like the divider.
*/
-class AppPair implements ShellTaskOrganizer.TaskListener {
+class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChangeListener {
private static final String TAG = AppPair.class.getSimpleName();
private ActivityManager.RunningTaskInfo mRootTaskInfo;
@@ -55,7 +57,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
private final AppPairsController mController;
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
- private AppPairLayout mAppPairLayout;
+ private SplitLayout mSplitLayout;
AppPair(AppPairsController controller) {
mController = controller;
@@ -92,11 +94,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
mTaskInfo1 = task1;
mTaskInfo2 = task2;
- mAppPairLayout = new AppPairLayout(
+ mSplitLayout = new SplitLayout(
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
- mDisplayController.getDisplay(mRootTaskInfo.displayId),
- mRootTaskInfo.configuration,
- mRootTaskLeash);
+ mRootTaskInfo.configuration, this, mRootTaskLeash);
final WindowContainerToken token1 = task1.token;
final WindowContainerToken token2 = task2.token;
@@ -107,8 +107,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
.reparent(token2, mRootTaskInfo.token, true /* onTop */)
.setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
.setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
- .setBounds(token1, mAppPairLayout.getBounds1())
- .setBounds(token2, mAppPairLayout.getBounds2())
+ .setBounds(token1, mSplitLayout.getBounds1())
+ .setBounds(token2, mSplitLayout.getBounds2())
// Moving the root task to top after the child tasks were repareted , or the root
// task cannot be visible and focused.
.reorder(mRootTaskInfo.token, true);
@@ -117,6 +117,10 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
}
void unpair() {
+ unpair(null /* toTopToken */);
+ }
+
+ private void unpair(@Nullable WindowContainerToken toTopToken) {
final WindowContainerToken token1 = mTaskInfo1.token;
final WindowContainerToken token2 = mTaskInfo2.token;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -124,23 +128,16 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
// Reparent out of this container and reset windowing mode.
wct.setHidden(mRootTaskInfo.token, true)
.reorder(mRootTaskInfo.token, false)
- .reparent(token1, null, false /* onTop */)
- .reparent(token2, null, false /* onTop */)
+ .reparent(token1, null, token1 == toTopToken /* onTop */)
+ .reparent(token2, null, token2 == toTopToken /* onTop */)
.setWindowingMode(token1, WINDOWING_MODE_UNDEFINED)
.setWindowingMode(token2, WINDOWING_MODE_UNDEFINED);
mController.getTaskOrganizer().applyTransaction(wct);
mTaskInfo1 = null;
mTaskInfo2 = null;
- mAppPairLayout.release();
- mAppPairLayout = null;
- }
-
- void setVisible(boolean visible) {
- if (mAppPairLayout == null) {
- return;
- }
- mAppPairLayout.setDividerVisibility(visible);
+ mSplitLayout.release();
+ mSplitLayout = null;
}
@Override
@@ -160,37 +157,46 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
if (mTaskLeash1 == null || mTaskLeash2 == null) return;
- setVisible(true);
- final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
- final Rect dividerBounds = mAppPairLayout.getDividerBounds();
+ mSplitLayout.init();
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ final Rect dividerBounds = mSplitLayout.getDividerBounds();
// TODO: Is there more we need to do here?
- mSyncQueue.runInSync(t -> t
- .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
- mTaskInfo1.positionInParent.y)
- .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
- mTaskInfo2.positionInParent.y)
- .setLayer(dividerLeash, Integer.MAX_VALUE)
- .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
- .show(mRootTaskLeash)
- .show(dividerLeash)
- .show(mTaskLeash1)
- .show(mTaskLeash2));
+ mSyncQueue.runInSync(t -> {
+ t.setLayer(dividerLeash, Integer.MAX_VALUE)
+ .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
+ mTaskInfo1.positionInParent.y)
+ .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
+ mTaskInfo2.positionInParent.y)
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ .show(mRootTaskLeash)
+ .show(mTaskLeash1)
+ .show(mTaskLeash2);
+ });
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo.taskId == getRootTaskId()) {
+ if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
+ mSyncQueue.runInSync(t -> {
+ if (taskInfo.isVisible) {
+ t.show(mRootTaskLeash);
+ } else {
+ t.hide(mRootTaskLeash);
+ }
+ });
+ }
mRootTaskInfo = taskInfo;
- if (mAppPairLayout != null
- && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) {
- // Update bounds when there is root bounds or orientation changed.
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
+ // Update bounds when root bounds or its orientation changed.
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
- final Rect dividerBounds = mAppPairLayout.getDividerBounds();
- final Rect bounds1 = mAppPairLayout.getBounds1();
- final Rect bounds2 = mAppPairLayout.getBounds2();
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ final Rect dividerBounds = mSplitLayout.getDividerBounds();
+ final Rect bounds1 = mSplitLayout.getBounds1();
+ final Rect bounds2 = mSplitLayout.getBounds2();
wct.setBounds(mTaskInfo1.token, bounds1)
.setBounds(mTaskInfo2.token, bounds2);
@@ -198,7 +204,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
mSyncQueue.runInSync(t -> t
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top)
- .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top));
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ // Resets layer to divider bar to make sure it is always on top.
+ .setLayer(dividerLeash, Integer.MAX_VALUE));
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
@@ -240,4 +248,39 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
public String toString() {
return TAG + "#" + getRootTaskId();
}
+
+ @Override
+ public void onSnappedToDismiss(boolean snappedToEnd) {
+ unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */);
+ }
+
+ @Override
+ public void onBoundsChanging(SplitLayout layout) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) return;
+ final Rect dividerBounds = layout.getDividerBounds();
+ final Rect bounds1 = layout.getBounds1();
+ final Rect bounds2 = layout.getBounds2();
+ mSyncQueue.runInSync(t -> t
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ .setPosition(mTaskLeash1, bounds1.left, bounds1.top)
+ .setPosition(mTaskLeash2, bounds2.left, bounds2.top));
+ }
+
+ @Override
+ public void onBoundsChanged(SplitLayout layout) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) return;
+ final Rect dividerBounds = layout.getDividerBounds();
+ final Rect bounds1 = layout.getBounds1();
+ final Rect bounds2 = layout.getBounds2();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mTaskInfo1.token, bounds1)
+ .setBounds(mTaskInfo2.token, bounds2);
+ mController.getTaskOrganizer().applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ .setPosition(mTaskLeash1, bounds1.left, bounds1.top)
+ .setPosition(mTaskLeash2, bounds2.left, bounds2.top));
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java
deleted file mode 100644
index f8703f7ec0bc..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2020 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.apppairs;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.Display;
-import android.view.IWindow;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-
-import com.android.wm.shell.R;
-
-/**
- * Records and handles layout of a pair of apps.
- */
-// TODO(172704238): add tests
-final class AppPairLayout {
- private static final String DIVIDER_WINDOW_TITLE = "AppPairDivider";
- private final Context mContext;
- private final AppPairWindowManager mAppPairWindowManager;
- private final SurfaceControlViewHost mViewHost;
-
- private final int mDividerWindowWidth;
- private final int mDividerWindowInsets;
-
- private boolean mIsLandscape;
- private Rect mRootBounds;
- private DIVIDE_POLICY mDividePolicy;
-
- private DividerView mDividerView;
- private SurfaceControl mDividerLeash;
-
- AppPairLayout(
- Context context,
- Display display,
- Configuration configuration,
- SurfaceControl rootLeash) {
- mContext = context.createConfigurationContext(configuration);
- mIsLandscape = isLandscape(configuration);
- mRootBounds = configuration.windowConfiguration.getBounds();
- mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- mDividerWindowInsets = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
-
- mAppPairWindowManager = new AppPairWindowManager(configuration, rootLeash);
- mViewHost = new SurfaceControlViewHost(mContext, display, mAppPairWindowManager);
- mDividePolicy = DIVIDE_POLICY.MIDDLE;
- mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
- }
-
- boolean updateConfiguration(Configuration configuration) {
- mAppPairWindowManager.setConfiguration(configuration);
- final Rect rootBounds = configuration.windowConfiguration.getBounds();
- final boolean isLandscape = isLandscape(configuration);
- if (mIsLandscape == isLandscape && isIdenticalBounds(mRootBounds, rootBounds)) {
- return false;
- }
-
- mIsLandscape = isLandscape;
- mRootBounds = rootBounds;
- mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
- mViewHost.relayout(
- mDividePolicy.mDividerBounds.width(),
- mDividePolicy.mDividerBounds.height());
- // TODO(172704238): handle divider bar rotation.
- return true;
- }
-
- Rect getBounds1() {
- return mDividePolicy.mBounds1;
- }
-
- Rect getBounds2() {
- return mDividePolicy.mBounds2;
- }
-
- Rect getDividerBounds() {
- return mDividePolicy.mDividerBounds;
- }
-
- SurfaceControl getDividerLeash() {
- return mDividerLeash;
- }
-
- void release() {
- if (mViewHost == null) return;
- mViewHost.release();
- }
-
- void setDividerVisibility(boolean visible) {
- if (mDividerView == null) {
- initDivider();
- }
- if (visible) {
- mDividerView.show();
- } else {
- mDividerView.hide();
- }
- }
-
- private void initDivider() {
- final DividerView dividerView = (DividerView) LayoutInflater.from(mContext)
- .inflate(R.layout.split_divider, null);
-
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- mDividePolicy.mDividerBounds.width(),
- mDividePolicy.mDividerBounds.height(),
- TYPE_DOCK_DIVIDER,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
- PixelFormat.TRANSLUCENT);
- lp.token = new Binder();
- lp.setTitle(DIVIDER_WINDOW_TITLE);
- lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
-
- mViewHost.setView(dividerView, lp);
- mDividerView = dividerView;
- mDividerLeash = mAppPairWindowManager.getSurfaceControl(mViewHost.getWindowToken());
- }
-
- private static boolean isLandscape(Configuration configuration) {
- return configuration.orientation == ORIENTATION_LANDSCAPE;
- }
-
- private static boolean isIdenticalBounds(Rect bounds1, Rect bounds2) {
- return bounds1.left == bounds2.left && bounds1.top == bounds2.top
- && bounds1.right == bounds2.right && bounds1.bottom == bounds2.bottom;
- }
-
- /**
- * Indicates the policy of placing divider bar and corresponding split-screens.
- */
- // TODO(172704238): add more divide policy and provide snap to resize feature for divider bar.
- enum DIVIDE_POLICY {
- MIDDLE;
-
- void update(boolean isLandscape, Rect rootBounds, int dividerWindowWidth,
- int dividerWindowInsets) {
- final int dividerOffset = dividerWindowWidth / 2;
- final int boundsOffset = dividerOffset - dividerWindowInsets;
-
- mDividerBounds = new Rect(rootBounds);
- mBounds1 = new Rect(rootBounds);
- mBounds2 = new Rect(rootBounds);
-
- switch (this) {
- case MIDDLE:
- default:
- if (isLandscape) {
- mDividerBounds.left = rootBounds.width() / 2 - dividerOffset;
- mDividerBounds.right = rootBounds.width() / 2 + dividerOffset;
- mBounds1.left = rootBounds.width() / 2 + boundsOffset;
- mBounds2.right = rootBounds.width() / 2 - boundsOffset;
- } else {
- mDividerBounds.top = rootBounds.height() / 2 - dividerOffset;
- mDividerBounds.bottom = rootBounds.height() / 2 + dividerOffset;
- mBounds1.bottom = rootBounds.height() / 2 - boundsOffset;
- mBounds2.top = rootBounds.height() / 2 + boundsOffset;
- }
- }
- }
-
- Rect mDividerBounds;
- Rect mBounds1;
- Rect mBounds2;
- }
-
- /**
- * WindowManger for app pair. Holds view hierarchy for the root task.
- */
- private static final class AppPairWindowManager extends WindowlessWindowManager {
- AppPairWindowManager(Configuration config, SurfaceControl rootSurface) {
- super(config, rootSurface, null /* hostInputToken */);
- }
-
- @Override
- public void setTouchRegion(IBinder window, Region region) {
- super.setTouchRegion(window, region);
- }
-
- @Override
- public SurfaceControl getSurfaceControl(IWindow window) {
- return super.getSurfaceControl(window);
- }
-
- @Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index af06764145e3..f5aa852c87ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -20,11 +20,14 @@ import android.app.ActivityManager;
import androidx.annotation.NonNull;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.PrintWriter;
/**
* Interface to engage app pairs feature.
*/
+@ExternalThread
public interface AppPairs {
/** Pairs indicated tasks. */
boolean pair(int task1, int task2);
@@ -36,6 +39,4 @@ public interface AppPairs {
void dump(@NonNull PrintWriter pw, String prefix);
/** Called when the shell organizer has been registered. */
void onOrganizerRegistered();
- /** Called when the visibility of the keyguard changes. */
- void onKeyguardVisibilityChanged(boolean showing);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index 925a4f36d5e6..f2f09820639a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.apppairs;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager;
@@ -30,15 +28,13 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerCallback;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import java.io.PrintWriter;
/**
* Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}.
*/
-public class AppPairsController implements AppPairs, TaskStackListenerCallback {
+public class AppPairsController implements AppPairs {
private static final String TAG = AppPairsController.class.getSimpleName();
private final ShellTaskOrganizer mTaskOrganizer;
@@ -48,14 +44,12 @@ public class AppPairsController implements AppPairs, TaskStackListenerCallback {
// Active app-pairs mapped by root task id key.
private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
private final DisplayController mDisplayController;
- private int mForegroundTaskId = INVALID_TASK_ID;
public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController, TaskStackListenerImpl taskStackListener) {
+ DisplayController displayController) {
mTaskOrganizer = organizer;
mSyncQueue = syncQueue;
mDisplayController = displayController;
- taskStackListener.addListener(this);
}
@Override
@@ -71,27 +65,6 @@ public class AppPairsController implements AppPairs, TaskStackListenerCallback {
}
@Override
- public void onTaskMovedToFront(int taskId) {
- mForegroundTaskId = INVALID_TASK_ID;
- for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
- final AppPair candidate = mActiveAppPairs.valueAt(i);
- final boolean containForegroundTask = candidate.contains(taskId);
- candidate.setVisible(containForegroundTask);
- if (containForegroundTask) {
- mForegroundTaskId = candidate.getRootTaskId();
- }
- }
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (mForegroundTaskId == INVALID_TASK_ID) {
- return;
- }
- mActiveAppPairs.get(mForegroundTaskId).setVisible(!showing);
- }
-
- @Override
public boolean pair(int taskId1, int taskId2) {
final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java
deleted file mode 100644
index 41b5e47e7fa9..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2020 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.apppairs;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Stack divider for app pair.
- */
-public class DividerView extends FrameLayout {
- public DividerView(@NonNull Context context) {
- super(context);
- }
-
- public DividerView(@NonNull Context context,
- @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- void show() {
- post(() -> setVisibility(View.VISIBLE));
- }
-
- void hide() {
- post(() -> setVisibility(View.INVISIBLE));
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index aa7355b61eda..40b41e11c8aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1213,7 +1213,7 @@ public class BubbleController implements Bubbles {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (mStackView != null) {
- mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
+ mStackView.onImeVisibilityChanged(imeVisible, imeHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 92d15c5feaca..fa5ac449cd54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -30,6 +30,8 @@ import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -40,6 +42,7 @@ import java.util.function.IntConsumer;
/**
* Interface to engage bubbles feature.
*/
+@ExternalThread
public interface Bubbles {
@Retention(SOURCE)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
deleted file mode 100644
index 96b9f86673fc..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2020 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;
-
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-
-import android.annotation.NonNull;
-import android.os.HandlerThread;
-import android.util.Singleton;
-
-/**
- * A singleton thread for Shell to run animations on.
- */
-public class AnimationThread extends HandlerThread {
- private ShellExecutor mExecutor;
-
- private AnimationThread() {
- super("wmshell.anim", THREAD_PRIORITY_DISPLAY);
- }
-
- /** Get the singleton instance of this thread */
- public static AnimationThread instance() {
- return sAnimationThreadSingleton.get();
- }
-
- /**
- * @return a shared {@link ShellExecutor} associated with this thread
- * @hide
- */
- @NonNull
- public ShellExecutor getExecutor() {
- if (mExecutor == null) {
- mExecutor = new HandlerExecutor(getThreadHandler());
- }
- return mExecutor;
- }
-
- private static final Singleton<AnimationThread> sAnimationThreadSingleton =
- new Singleton<AnimationThread>() {
- @Override
- protected AnimationThread create() {
- final AnimationThread animThread = new AnimationThread();
- animThread.start();
- return animThread;
- }
- };
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 3263f79888d6..cb4584c41184 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -23,6 +23,10 @@ import android.view.IDisplayWindowRotationController;
import android.view.IWindowManager;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
import java.util.ArrayList;
/**
@@ -35,39 +39,18 @@ public class DisplayChangeController {
private final Handler mHandler;
private final IWindowManager mWmService;
+ private final IDisplayWindowRotationController mControllerImpl;
private final ArrayList<OnDisplayChangingListener> mRotationListener =
new ArrayList<>();
private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>();
- private final IDisplayWindowRotationController mDisplayRotationController =
- new IDisplayWindowRotationController.Stub() {
- @Override
- public void onRotateDisplay(int displayId, final int fromRotation,
- final int toRotation, IDisplayWindowRotationCallback callback) {
- mHandler.post(() -> {
- WindowContainerTransaction t = new WindowContainerTransaction();
- synchronized (mRotationListener) {
- mTmpListeners.clear();
- // Make a local copy in case the handlers add/remove themselves.
- mTmpListeners.addAll(mRotationListener);
- }
- for (OnDisplayChangingListener c : mTmpListeners) {
- c.onRotateDisplay(displayId, fromRotation, toRotation, t);
- }
- try {
- callback.continueRotateDisplay(toRotation, t);
- } catch (RemoteException e) {
- }
- });
- }
- };
-
public DisplayChangeController(Handler mainHandler, IWindowManager wmService) {
mHandler = mainHandler;
mWmService = wmService;
+ mControllerImpl = new DisplayWindowRotationControllerImpl();
try {
- mWmService.setDisplayWindowRotationController(mDisplayRotationController);
+ mWmService.setDisplayWindowRotationController(mControllerImpl);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register rotation controller");
}
@@ -91,10 +74,41 @@ public class DisplayChangeController {
}
}
+ private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
+ IDisplayWindowRotationCallback callback) {
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ synchronized (mRotationListener) {
+ mTmpListeners.clear();
+ // Make a local copy in case the handlers add/remove themselves.
+ mTmpListeners.addAll(mRotationListener);
+ }
+ for (OnDisplayChangingListener c : mTmpListeners) {
+ c.onRotateDisplay(displayId, fromRotation, toRotation, t);
+ }
+ try {
+ callback.continueRotateDisplay(toRotation, t);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @BinderThread
+ private class DisplayWindowRotationControllerImpl
+ extends IDisplayWindowRotationController.Stub {
+ @Override
+ public void onRotateDisplay(int displayId, final int fromRotation,
+ final int toRotation, IDisplayWindowRotationCallback callback) {
+ mHandler.post(() -> {
+ DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation,
+ callback);
+ });
+ }
+ }
+
/**
* Give a listener a chance to queue up configuration changes to execute as part of a
* display rotation. The contents of {@link #onRotateDisplay} must run synchronously.
*/
+ @ShellMainThread
public interface OnDisplayChangingListener {
/**
* Called before the display is rotated. Contents of this method must run synchronously.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 418973204add..a413c052cb6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -28,7 +28,10 @@ import android.view.Display;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
+import androidx.annotation.BinderThread;
+
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import java.util.ArrayList;
@@ -45,6 +48,7 @@ public class DisplayController {
private final Context mContext;
private final IWindowManager mWmService;
private final DisplayChangeController mChangeController;
+ private final IDisplayWindowListener mDisplayContainerListener;
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
@@ -57,119 +61,13 @@ public class DisplayController {
return displayManager.getDisplay(displayId);
}
- private final IDisplayWindowListener mDisplayContainerListener =
- new IDisplayWindowListener.Stub() {
- @Override
- public void onDisplayAdded(int displayId) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) != null) {
- return;
- }
- Display display = getDisplay(displayId);
- if (display == null) {
- // It's likely that the display is private to some app and thus not
- // accessible by system-ui.
- return;
- }
- DisplayRecord record = new DisplayRecord();
- record.mDisplayId = displayId;
- record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
- : mContext.createDisplayContext(display);
- record.mDisplayLayout = new DisplayLayout(record.mContext, display);
- mDisplays.put(displayId, record);
- for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
- mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
- }
- }
- });
- }
-
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- DisplayRecord dr = mDisplays.get(displayId);
- if (dr == null) {
- Slog.w(TAG, "Skipping Display Configuration change on non-added"
- + " display.");
- return;
- }
- Display display = getDisplay(displayId);
- if (display == null) {
- Slog.w(TAG, "Skipping Display Configuration change on invalid"
- + " display. It may have been removed.");
- return;
- }
- Context perDisplayContext = mContext;
- if (displayId != Display.DEFAULT_DISPLAY) {
- perDisplayContext = mContext.createDisplayContext(display);
- }
- dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
- dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
- for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
- mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
- displayId, newConfig);
- }
- }
- });
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) == null) {
- return;
- }
- for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
- }
- mDisplays.remove(displayId);
- }
- });
- }
-
- @Override
- public void onFixedRotationStarted(int displayId, int newRotation) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
- Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
- + " display, displayId=" + displayId);
- return;
- }
- for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onFixedRotationStarted(
- displayId, newRotation);
- }
- }
- });
- }
-
- @Override
- public void onFixedRotationFinished(int displayId) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
- Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
- + " display, displayId=" + displayId);
- return;
- }
- for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
- }
- }
- });
- }
- };
-
public DisplayController(Context context, Handler handler,
IWindowManager wmService) {
mHandler = handler;
mContext = context;
mWmService = wmService;
mChangeController = new DisplayChangeController(mHandler, mWmService);
+ mDisplayContainerListener = new DisplayWindowListenerImpl();
try {
mWmService.registerDisplayWindowListener(mDisplayContainerListener);
} catch (RemoteException e) {
@@ -232,18 +130,146 @@ public class DisplayController {
mChangeController.removeRotationListener(controller);
}
+ private void onDisplayAdded(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) != null) {
+ return;
+ }
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ // It's likely that the display is private to some app and thus not
+ // accessible by system-ui.
+ return;
+ }
+ DisplayRecord record = new DisplayRecord();
+ record.mDisplayId = displayId;
+ record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+ : mContext.createDisplayContext(display);
+ record.mDisplayLayout = new DisplayLayout(record.mContext, display);
+ mDisplays.put(displayId, record);
+ for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+ mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
+ }
+ }
+ }
+
+ private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ synchronized (mDisplays) {
+ DisplayRecord dr = mDisplays.get(displayId);
+ if (dr == null) {
+ Slog.w(TAG, "Skipping Display Configuration change on non-added"
+ + " display.");
+ return;
+ }
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ Slog.w(TAG, "Skipping Display Configuration change on invalid"
+ + " display. It may have been removed.");
+ return;
+ }
+ Context perDisplayContext = mContext;
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ perDisplayContext = mContext.createDisplayContext(display);
+ }
+ dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
+ dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
+ for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+ mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
+ displayId, newConfig);
+ }
+ }
+ }
+
+ private void onDisplayRemoved(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null) {
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
+ }
+ mDisplays.remove(displayId);
+ }
+ }
+
+ private void onFixedRotationStarted(int displayId, int newRotation) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onFixedRotationStarted(
+ displayId, newRotation);
+ }
+ }
+ }
+
+ private void onFixedRotationFinished(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
+ }
+ }
+ }
+
private static class DisplayRecord {
int mDisplayId;
Context mContext;
DisplayLayout mDisplayLayout;
}
+ @BinderThread
+ private class DisplayWindowListenerImpl extends IDisplayWindowListener.Stub {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ mHandler.post(() -> {
+ DisplayController.this.onDisplayAdded(displayId);
+ });
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ mHandler.post(() -> {
+ DisplayController.this.onDisplayConfigurationChanged(displayId, newConfig);
+ });
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ mHandler.post(() -> {
+ DisplayController.this.onDisplayRemoved(displayId);
+ });
+ }
+
+ @Override
+ public void onFixedRotationStarted(int displayId, int newRotation) {
+ mHandler.post(() -> {
+ DisplayController.this.onFixedRotationStarted(displayId, newRotation);
+ });
+ }
+
+ @Override
+ public void onFixedRotationFinished(int displayId) {
+ mHandler.post(() -> {
+ DisplayController.this.onFixedRotationFinished(displayId);
+ });
+ }
+ }
+
/**
* Gets notified when a display is added/removed to the WM hierarchy and when a display's
* window-configuration changes.
*
* @see IDisplayWindowListener
*/
+ @ShellMainThread
public interface OnDisplaysChangedListener {
/**
* Called when a display has been added to the WM hierarchy.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index ea18a19c2ee5..3fbd7ed0ec5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -24,7 +24,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
@@ -40,6 +39,8 @@ import android.view.WindowInsets;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import androidx.annotation.BinderThread;
+
import com.android.internal.view.IInputMethodManager;
import java.util.ArrayList;
@@ -197,6 +198,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mRotation = initialRotation;
}
+ @BinderThread
@Override
public void insetsChanged(InsetsState insetsState) {
mExecutor.execute(() -> {
@@ -204,6 +206,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
return;
}
+ mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+
final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
final Rect newFrame = newSource.getFrame();
final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
@@ -216,6 +220,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
});
}
+ @BinderThread
@Override
public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {
@@ -266,6 +271,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
+ @BinderThread
@Override
public void showInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
@@ -275,6 +281,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */));
}
+ @BinderThread
@Override
public void hideInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
@@ -284,6 +291,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */));
}
+ @BinderThread
@Override
public void topFocusedWindowChanged(String packageName) {
// no-op
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index cd75840b8c71..fa0a75c2d364 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -28,6 +28,17 @@ public class HandlerExecutor implements ShellExecutor {
}
@Override
+ public void execute(@NonNull Runnable command) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ command.run();
+ return;
+ }
+ if (!mHandler.post(command)) {
+ throw new RuntimeException(mHandler + " is probably exiting");
+ }
+ }
+
+ @Override
public void executeDelayed(@NonNull Runnable r, long delayMillis) {
if (!mHandler.postDelayed(r, delayMillis)) {
throw new RuntimeException(mHandler + " is probably exiting");
@@ -38,11 +49,4 @@ public class HandlerExecutor implements ShellExecutor {
public void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
-
- @Override
- public void execute(@NonNull Runnable command) {
- if (!mHandler.post(command)) {
- throw new RuntimeException(mHandler + " is probably exiting");
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index aafe2407a1ea..22b831b7565e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -16,13 +16,40 @@
package com.android.wm.shell.common;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* Super basic Executor interface that adds support for delayed execution and removing callbacks.
* Intended to wrap Handler while better-supporting testing.
*/
public interface ShellExecutor extends Executor {
+
+ /**
+ * Executes the given runnable. If the caller is running on the same looper as this executor,
+ * the runnable must be executed immediately.
+ */
+ @Override
+ void execute(Runnable runnable);
+
+ /**
+ * Executes the given runnable in a blocking call. If the caller is running on the same looper
+ * as this executor, the runnable must be executed immediately.
+ *
+ * @throws InterruptedException if runnable does not return in the time specified by
+ * {@param waitTimeout}
+ */
+ default void executeBlocking(Runnable runnable, int waitTimeout, TimeUnit waitTimeUnit)
+ throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ execute(() -> {
+ runnable.run();
+ latch.countDown();
+ });
+ latch.await(waitTimeout, waitTimeUnit);
+ }
+
/**
* See {@link android.os.Handler#postDelayed(Runnable, long)}.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index 9cb125087cd9..7321dc88770d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -24,6 +24,10 @@ import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
import java.util.ArrayList;
/**
@@ -151,6 +155,7 @@ public final class SyncTransactionQueue {
mHandler.postDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
+ @BinderThread
@Override
public void onTransactionReady(int id,
@NonNull SurfaceControl.Transaction t) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 0f6dd93f9c16..5e077188c415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -19,12 +19,10 @@ package com.android.wm.shell.common;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ITaskStackListener;
-import android.app.TaskInfo;
import android.content.ComponentName;
import android.os.IBinder;
import androidx.annotation.BinderThread;
-import androidx.annotation.MainThread;
/**
* An interface to track task stack changes. Classes should implement this instead of
@@ -32,85 +30,61 @@ import androidx.annotation.MainThread;
*/
public interface TaskStackListenerCallback {
- @MainThread
default void onRecentTaskListUpdated() { }
- @MainThread
default void onRecentTaskListFrozenChanged(boolean frozen) { }
@BinderThread
default void onTaskStackChangedBackground() { }
- @MainThread
default void onTaskStackChanged() { }
- @MainThread
default void onTaskProfileLocked(int taskId, int userId) { }
- @MainThread
default void onTaskDisplayChanged(int taskId, int newDisplayId) { }
- @MainThread
default void onTaskCreated(int taskId, ComponentName componentName) { }
- @MainThread
default void onTaskRemoved(int taskId) { }
- @MainThread
default void onTaskMovedToFront(int taskId) { }
- @MainThread
default void onTaskMovedToFront(RunningTaskInfo taskInfo) {
onTaskMovedToFront(taskInfo.taskId);
}
- @MainThread
default void onTaskDescriptionChanged(RunningTaskInfo taskInfo) { }
- @MainThread
default void onTaskSnapshotChanged(int taskId, ActivityManager.TaskSnapshot snapshot) { }
- @MainThread
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { }
- @MainThread
default void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
boolean clearedTask, boolean wasVisible) { }
- @MainThread
default void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
- @MainThread
default void onActivityUnpinned() { }
- @MainThread
default void onActivityForcedResizable(String packageName, int taskId, int reason) { }
- @MainThread
default void onActivityDismissingDockedStack() { }
- @MainThread
default void onActivityLaunchOnSecondaryDisplayFailed() { }
- @MainThread
default void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo) {
onActivityLaunchOnSecondaryDisplayFailed();
}
- @MainThread
default void onActivityLaunchOnSecondaryDisplayRerouted() { }
- @MainThread
default void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo) {
onActivityLaunchOnSecondaryDisplayRerouted();
}
- @MainThread
default void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
- @MainThread
default void onActivityRotation(int displayId) { }
- @MainThread
default void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
new file mode 100644
index 000000000000..4009ad21b9b8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
@@ -0,0 +1,18 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync
+ * instead of the app vsync.
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ChoreographerSfVsync {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
new file mode 100644
index 000000000000..7560f71d1f98
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
@@ -0,0 +1,15 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or class that is called from an external thread to the Shell threads. */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExternalThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
new file mode 100644
index 000000000000..0479f8780c79
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
@@ -0,0 +1,15 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellAnimationThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
new file mode 100644
index 000000000000..423f4ce3bfd4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
@@ -0,0 +1,15 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or qualifies a provider that runs on the Shell main-thread */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellMainThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
new file mode 100644
index 000000000000..50d9fe8629ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 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.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceControlViewHost;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+
+/**
+ * Stack divider for app pair.
+ */
+// TODO(b/172704238): add handle view to indicate touching status.
+public class DividerView extends FrameLayout implements View.OnTouchListener {
+ private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+
+ private SplitLayout mSplitLayout;
+ private SurfaceControlViewHost mViewHost;
+ private DragListener mDragListener;
+
+ private VelocityTracker mVelocityTracker;
+ private boolean mMoving;
+ private int mStartPos;
+
+ public DividerView(@NonNull Context context) {
+ super(context);
+ }
+
+ public DividerView(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /** Sets up essential dependencies of the divider bar. */
+ public void setup(
+ SplitLayout layout,
+ SurfaceControlViewHost viewHost,
+ @Nullable DragListener dragListener) {
+ mSplitLayout = layout;
+ mViewHost = viewHost;
+ mDragListener = dragListener;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setOnTouchListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mSplitLayout == null) {
+ return false;
+ }
+
+ final int action = event.getAction() & MotionEvent.ACTION_MASK;
+ final boolean isLandscape = isLandscape();
+ // Using raw xy to prevent lost track of motion events while moving divider bar.
+ final int touchPos = isLandscape ? (int) event.getRawX() : (int) event.getRawY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(event);
+ setSlippery(false);
+ mStartPos = touchPos;
+ mMoving = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mVelocityTracker.addMovement(event);
+ if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) {
+ mStartPos = touchPos;
+ mMoving = true;
+ if (mDragListener != null) {
+ mDragListener.onDragStart();
+ }
+ }
+ if (mMoving) {
+ final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ mSplitLayout.updateDividePosition(position);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(1000 /* units */);
+ final float velocity = isLandscape
+ ? mVelocityTracker.getXVelocity()
+ : mVelocityTracker.getYVelocity();
+ setSlippery(true);
+ mMoving = false;
+ if (mDragListener != null) {
+ mDragListener.onDragEnd();
+ }
+
+ final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ final DividerSnapAlgorithm.SnapTarget snapTarget =
+ mSplitLayout.findSnapTarget(position, velocity);
+ mSplitLayout.setSnapTarget(snapTarget);
+ break;
+ }
+ return true;
+ }
+
+ private void setSlippery(boolean slippery) {
+ if (mViewHost == null) {
+ return;
+ }
+
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
+ final boolean isSlippery = (lp.flags & FLAG_SLIPPERY) != 0;
+ if (isSlippery == slippery) {
+ return;
+ }
+
+ if (slippery) {
+ lp.flags |= FLAG_SLIPPERY;
+ } else {
+ lp.flags &= ~FLAG_SLIPPERY;
+ }
+ mViewHost.relayout(lp);
+ }
+
+ private boolean isLandscape() {
+ return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ }
+
+ /** Monitors dragging action of the divider bar. */
+ // TODO(b/172704238): add listeners to deal with resizing state of the app windows.
+ public interface DragListener {
+ /** Called when start dragging. */
+ void onDragStart();
+ /** Called when stop dragging. */
+ void onDragEnd();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
new file mode 100644
index 000000000000..e11037f55cfa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2020 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.split;
+
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+
+/**
+ * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
+ * divide position changes.
+ */
+public class SplitLayout {
+ private final int mDividerWindowWidth;
+ private final int mDividerInsets;
+ private final int mDividerSize;
+
+ private final Rect mRootBounds = new Rect();
+ private final Rect mDividerBounds = new Rect();
+ private final Rect mBounds1 = new Rect();
+ private final Rect mBounds2 = new Rect();
+ private final LayoutChangeListener mLayoutChangeListener;
+ private final SplitWindowManager mSplitWindowManager;
+
+ private Context mContext;
+ private DividerSnapAlgorithm mDividerSnapAlgorithm;
+ private int mDividePosition;
+
+ public SplitLayout(Context context, Configuration configuration,
+ LayoutChangeListener layoutChangeListener, SurfaceControl rootLeash) {
+ mContext = context.createConfigurationContext(configuration);
+ mLayoutChangeListener = layoutChangeListener;
+ mSplitWindowManager = new SplitWindowManager(mContext, configuration, rootLeash);
+
+ mDividerWindowWidth = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ mDividerInsets = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+ mDividerSize = mDividerWindowWidth - mDividerInsets * 2;
+
+ mRootBounds.set(configuration.windowConfiguration.getBounds());
+ mDividerSnapAlgorithm = getSnapAlgorithm(context.getResources(), mRootBounds);
+ mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividePosition);
+ }
+
+ /** Gets bounds of the primary split. */
+ public Rect getBounds1() {
+ return mBounds1;
+ }
+
+ /** Gets bounds of the secondary split. */
+ public Rect getBounds2() {
+ return mBounds2;
+ }
+
+ /** Gets bounds of divider window. */
+ public Rect getDividerBounds() {
+ return mDividerBounds;
+ }
+
+ /** Returns leash of the current divider bar. */
+ @Nullable
+ public SurfaceControl getDividerLeash() {
+ return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
+ }
+
+ int getDividePosition() {
+ return mDividePosition;
+ }
+
+ /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
+ public boolean updateConfiguration(Configuration configuration) {
+ final Rect rootBounds = configuration.windowConfiguration.getBounds();
+ if (mRootBounds.equals(rootBounds)) {
+ return false;
+ }
+
+ mContext = mContext.createConfigurationContext(configuration);
+ mSplitWindowManager.setConfiguration(configuration);
+ mRootBounds.set(rootBounds);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext.getResources(), mRootBounds);
+ mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividePosition);
+ release();
+ init();
+ return true;
+ }
+
+ /** Updates recording bounds of divider window and both of the splits. */
+ private void updateBounds(int position) {
+ mDividerBounds.set(mRootBounds);
+ mBounds1.set(mRootBounds);
+ mBounds2.set(mRootBounds);
+ if (isLandscape(mRootBounds)) {
+ mDividerBounds.left = position - mDividerInsets;
+ mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth;
+ mBounds1.right = mBounds1.left + position;
+ mBounds2.left = mBounds1.right + mDividerSize;
+ } else {
+ mDividerBounds.top = position - mDividerInsets;
+ mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth;
+ mBounds1.bottom = mBounds1.top + position;
+ mBounds2.top = mBounds1.bottom + mDividerSize;
+ }
+ }
+
+ /** Inflates {@link DividerView} on the root surface. */
+ public void init() {
+ mSplitWindowManager.init(this);
+ }
+
+ /** Releases the surface holding the current {@link DividerView}. */
+ public void release() {
+ mSplitWindowManager.release();
+ }
+
+ /**
+ * Updates bounds with the passing position. Usually used to update recording bounds while
+ * performing animation or dragging divider bar to resize the splits.
+ */
+ public void updateDividePosition(int position) {
+ updateBounds(position);
+ mLayoutChangeListener.onBoundsChanging(this);
+ }
+
+ /**
+ * Sets new divide position and updates bounds correspondingly. Notifies listener if the new
+ * target indicates dismissing split.
+ */
+ public void setSnapTarget(DividerSnapAlgorithm.SnapTarget snapTarget) {
+ switch(snapTarget.flag) {
+ case FLAG_DISMISS_START:
+ mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */);
+ break;
+ case FLAG_DISMISS_END:
+ mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */);
+ break;
+ default:
+ mDividePosition = snapTarget.position;
+ updateBounds(mDividePosition);
+ mLayoutChangeListener.onBoundsChanged(this);
+ break;
+ }
+ }
+
+ /**
+ * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity.
+ */
+ public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity) {
+ return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity);
+ }
+
+ private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) {
+ final boolean isLandscape = isLandscape(rootBounds);
+ return new DividerSnapAlgorithm(
+ resources,
+ rootBounds.width(),
+ rootBounds.height(),
+ mDividerSize,
+ !isLandscape,
+ new Rect() /* insets */,
+ isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
+ }
+
+ private static boolean isLandscape(Rect bounds) {
+ return bounds.width() > bounds.height();
+ }
+
+ /** Listens layout change event. */
+ public interface LayoutChangeListener {
+ /** Calls when dismissing split. */
+ void onSnappedToDismiss(boolean snappedToEnd);
+ /** Calls when the bounds is changing due to animation or dragging divider bar. */
+ void onBoundsChanging(SplitLayout layout);
+ /** Calls when the target bounds changed. */
+ void onBoundsChanged(SplitLayout layout);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
new file mode 100644
index 000000000000..e4121986bcb7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 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.split;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split.
+ */
+public final class SplitWindowManager extends WindowlessWindowManager {
+ private static final String DIVIDER_WINDOW_TITLE = "SplitDivider";
+
+ private Context mContext;
+ private SurfaceControlViewHost mViewHost;
+
+ public SplitWindowManager(Context context, Configuration config, SurfaceControl rootSurface) {
+ super(config, rootSurface, null /* hostInputToken */);
+ mContext = context.createConfigurationContext(config);
+ }
+
+ @Override
+ public void setTouchRegion(IBinder window, Region region) {
+ super.setTouchRegion(window, region);
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl(IWindow window) {
+ return super.getSurfaceControl(window);
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ mContext = mContext.createConfigurationContext(configuration);
+ }
+
+ /** Inflates {@link DividerView} on to the root surface. */
+ void init(SplitLayout splitLayout) {
+ if (mViewHost == null) {
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ }
+
+ final Rect dividerBounds = splitLayout.getDividerBounds();
+ final DividerView dividerView = (DividerView) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_divider, null /* root */);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
+ | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+ PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.setTitle(DIVIDER_WINDOW_TITLE);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ mViewHost.setView(dividerView, lp);
+ dividerView.setup(splitLayout, mViewHost, null /* dragListener */);
+ }
+
+ /**
+ * Releases the surface control of the current {@link DividerView} and tear down the view
+ * hierarchy.
+ */
+ void release() {
+ if (mViewHost == null) return;
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ /**
+ * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
+ * feasible.
+ */
+ @Nullable
+ SurfaceControl getSurfaceControl() {
+ return mViewHost == null ? null : getSurfaceControl(mViewHost.getWindowToken());
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 38e0519b7a90..3a2f0da6bf03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -20,11 +20,14 @@ import android.content.res.Configuration;
import androidx.annotation.NonNull;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.PrintWriter;
/**
* Interface to engage hide display cutout feature.
*/
+@ExternalThread
public interface HideDisplayCutout {
/**
* Notifies {@link Configuration} changed.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 9bb709f9a82a..821a00703adf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.onehanded;
import androidx.annotation.NonNull;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import java.io.PrintWriter;
@@ -25,6 +26,7 @@ import java.io.PrintWriter;
/**
* Interface to engage one handed feature.
*/
+@ExternalThread
public interface OneHanded {
/**
* Return one handed settings enabled or not.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index 993e0e7ed016..5593268588fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -19,12 +19,16 @@ package com.android.wm.shell.pip;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
import android.view.DisplayInfo;
-import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
+import android.view.WindowManagerGlobal;
+
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.ShellExecutor;
import java.util.ArrayList;
-import java.util.List;
/**
* PinnedStackListener that simply forwards all calls to each listener added via
@@ -32,8 +36,15 @@ import java.util.List;
* {@link com.android.server.wm.WindowManagerService#registerPinnedStackListener} replaces any
* previously set listener.
*/
-public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
- private List<PinnedStackListener> mListeners = new ArrayList<>();
+public class PinnedStackListenerForwarder {
+
+ private final IPinnedStackListener mListenerImpl = new PinnedStackListenerImpl();
+ private final ShellExecutor mShellMainExecutor;
+ private final ArrayList<PinnedStackListener> mListeners = new ArrayList<>();
+
+ public PinnedStackListenerForwarder(ShellExecutor shellMainExecutor) {
+ mShellMainExecutor = shellMainExecutor;
+ }
/** Adds a listener to receive updates from the WindowManagerService. */
public void addListener(PinnedStackListener listener) {
@@ -45,69 +56,110 @@ public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
mListeners.remove(listener);
}
- @Override
- public void onListenerRegistered(IPinnedStackController controller) {
- for (PinnedStackListener listener : mListeners) {
- listener.onListenerRegistered(controller);
- }
+ public void register(int displayId) throws RemoteException {
+ WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
+ displayId, mListenerImpl);
}
- @Override
- public void onMovementBoundsChanged(boolean fromImeAdjustment) {
+ private void onMovementBoundsChanged(boolean fromImeAdjustment) {
for (PinnedStackListener listener : mListeners) {
listener.onMovementBoundsChanged(fromImeAdjustment);
}
}
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ private void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
for (PinnedStackListener listener : mListeners) {
listener.onImeVisibilityChanged(imeVisible, imeHeight);
}
}
- @Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ private void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
for (PinnedStackListener listener : mListeners) {
listener.onActionsChanged(actions);
}
}
- @Override
- public void onActivityHidden(ComponentName componentName) {
+ private void onActivityHidden(ComponentName componentName) {
for (PinnedStackListener listener : mListeners) {
listener.onActivityHidden(componentName);
}
}
- @Override
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+ private void onDisplayInfoChanged(DisplayInfo displayInfo) {
for (PinnedStackListener listener : mListeners) {
listener.onDisplayInfoChanged(displayInfo);
}
}
- @Override
- public void onConfigurationChanged() {
+ private void onConfigurationChanged() {
for (PinnedStackListener listener : mListeners) {
listener.onConfigurationChanged();
}
}
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
+ private void onAspectRatioChanged(float aspectRatio) {
for (PinnedStackListener listener : mListeners) {
listener.onAspectRatioChanged(aspectRatio);
}
}
+ @BinderThread
+ private class PinnedStackListenerImpl extends IPinnedStackListener.Stub {
+ @Override
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onMovementBoundsChanged(fromImeAdjustment);
+ });
+ }
+
+ @Override
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight);
+ });
+ }
+
+ @Override
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onActionsChanged(actions);
+ });
+ }
+
+ @Override
+ public void onActivityHidden(ComponentName componentName) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onActivityHidden(componentName);
+ });
+ }
+
+ @Override
+ public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onDisplayInfoChanged(displayInfo);
+ });
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onConfigurationChanged();
+ });
+ }
+
+ @Override
+ public void onAspectRatioChanged(float aspectRatio) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio);
+ });
+ }
+ }
+
/**
* A counterpart of {@link IPinnedStackListener} with empty implementations.
* Subclasses can ignore those methods they do not intend to take action upon.
*/
public static class PinnedStackListener {
- public void onListenerRegistered(IPinnedStackController controller) {}
-
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 9fa222ad4fdd..da9ce0aacedc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -17,12 +17,12 @@
package com.android.wm.shell.pip;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import java.io.PrintWriter;
@@ -31,6 +31,7 @@ import java.util.function.Consumer;
/**
* Interface to engage picture in picture feature.
*/
+@ExternalThread
public interface Pip {
/**
* Closes PIP (PIPed activity and PIP system UI).
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index d82946269ee8..fe018115408a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -54,6 +54,7 @@ public class PipAnimationController {
public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3;
public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4;
public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
+ public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6;
@IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
TRANSITION_DIRECTION_NONE,
@@ -61,7 +62,8 @@ public class PipAnimationController {
TRANSITION_DIRECTION_TO_PIP,
TRANSITION_DIRECTION_LEAVE_PIP,
TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN,
- TRANSITION_DIRECTION_REMOVE_STACK
+ TRANSITION_DIRECTION_REMOVE_STACK,
+ TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
})
@Retention(RetentionPolicy.SOURCE)
public @interface TransitionDirection {}
@@ -109,13 +111,27 @@ public class PipAnimationController {
}
@SuppressWarnings("unchecked")
+ /**
+ * Construct and return an animator that animates from the {@param startBounds} to the
+ * {@param endBounds} with the given {@param direction}. If {@param direction} is type
+ * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate
+ * in a better, more smooth manner.
+ *
+ * In the case where one wants to start animation during an intermediate animation (for example,
+ * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate
+ * to the correct snap fraction region), then provide the base bounds, which is current PiP
+ * leash bounds before transformation/any animation. This is so when we try to construct
+ * the different transformation matrices for the animation, we are constructing this based off
+ * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
+ */
@VisibleForTesting
- public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
- Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) {
+ public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds,
+ Rect startBounds, Rect endBounds, Rect sourceHintRect,
+ @PipAnimationController.TransitionDirection int direction) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
- direction));
+ PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds,
+ sourceHintRect, direction));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -130,8 +146,8 @@ public class PipAnimationController {
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
- direction));
+ PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds,
+ sourceHintRect, direction));
}
return mCurrentAnimator;
}
@@ -180,6 +196,7 @@ public class PipAnimationController {
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
+ private T mBaseValue;
protected T mCurrentValue;
protected T mStartValue;
private T mEndValue;
@@ -190,10 +207,11 @@ public class PipAnimationController {
private @TransitionDirection int mTransitionDirection;
private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
- Rect destinationBounds, T startValue, T endValue) {
+ Rect destinationBounds, T baseValue, T startValue, T endValue) {
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
+ mBaseValue = baseValue;
mStartValue = startValue;
mEndValue = endValue;
addListener(this);
@@ -263,6 +281,10 @@ public class PipAnimationController {
return mStartValue;
}
+ T getBaseValue() {
+ return mBaseValue;
+ }
+
@VisibleForTesting
public T getEndValue() {
return mEndValue;
@@ -334,7 +356,7 @@ public class PipAnimationController {
static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
Rect destinationBounds, float startValue, float endValue) {
return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
- destinationBounds, startValue, endValue) {
+ destinationBounds, startValue, startValue, endValue) {
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
@@ -367,7 +389,7 @@ public class PipAnimationController {
}
static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
- Rect startValue, Rect endValue, Rect sourceHintRect,
+ Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction) {
// Just for simplicity we'll interpolate between the source rect hint insets and empty
// insets to calculate the window crop
@@ -375,7 +397,7 @@ public class PipAnimationController {
if (isOutPipDirection(direction)) {
initialSourceValue = new Rect(endValue);
} else {
- initialSourceValue = new Rect(startValue);
+ initialSourceValue = new Rect(baseValue);
}
final Rect sourceHintRectInsets;
@@ -391,22 +413,24 @@ public class PipAnimationController {
// construct new Rect instances in case they are recycled
return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
- endValue, new Rect(startValue), new Rect(endValue)) {
+ endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
+ final Rect base = getBaseValue();
final Rect start = getStartValue();
final Rect end = getEndValue();
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
+
if (isOutPipDirection(direction)) {
getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
+ getSurfaceTransactionHelper().scale(tx, leash, base, bounds);
}
} else {
final Rect insets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
new file mode 100644
index 000000000000..8d9ad4d1b96c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 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.pip;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.RemoteAction;
+import android.content.pm.ParceledListSlice;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+/**
+ * Interface to allow {@link com.android.wm.shell.pip.PipTaskOrganizer} to call into
+ * PiP menu when certain events happen (task appear/vanish, PiP move, etc.)
+ */
+public interface PipMenuController {
+
+ String MENU_WINDOW_TITLE = "PipMenuView";
+
+ /**
+ * Called when
+ * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
+ * is called.
+ */
+ void attach(SurfaceControl leash);
+
+ /**
+ * Called when
+ * {@link PipTaskOrganizer#onTaskVanished(RunningTaskInfo)} is called.
+ */
+ void detach();
+
+ /**
+ * Check if menu is visible or not.
+ */
+ boolean isMenuVisible();
+
+ /**
+ * Show the PIP menu.
+ */
+ void showMenu();
+
+ /**
+ * Given a set of actions, update the menu.
+ */
+ void setAppActions(ParceledListSlice<RemoteAction> appActions);
+
+ /**
+ * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
+ * need to synchronize the movements on the same frame as PiP.
+ */
+ default void resizePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {}
+
+ /**
+ * Move the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
+ * need to synchronize the movements on the same frame as PiP.
+ */
+ default void movePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {}
+
+ /**
+ * Update the PiP menu with the given bounds for re-layout purposes.
+ */
+ default void updateMenuBounds(Rect destinationBounds) {}
+
+ /**
+ * Returns a default LayoutParams for the PIP Menu.
+ * @param width the PIP stack width.
+ * @param height the PIP stack height.
+ */
+ default WindowManager.LayoutParams getPipMenuLayoutParams(String title, int width, int height) {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
+ lp.setTitle(title);
+ return lp;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 7cc2a419354e..167b9f9975f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -30,6 +30,7 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SNAP_AFTER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
@@ -63,7 +64,6 @@ import com.android.internal.os.SomeArgs;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.pip.phone.PipMenuActivityController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipUpdateThread;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -135,8 +135,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Handler mUpdateHandler;
private final PipBoundsState mPipBoundsState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
- // TODO(b/172286265): Remove dependency on .pip.PHONE.PipMenuActivityController
- private final PipMenuActivityController mMenuActivityController;
+ private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
private final PipUiEventLogger mPipUiEventLoggerLogger;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
@@ -264,7 +263,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public PipTaskOrganizer(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipBoundsAlgorithm boundsHandler,
- PipMenuActivityController menuActivityController,
+ @NonNull PipMenuController pipMenuController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
Optional<SplitScreen> splitScreenOptional,
@NonNull DisplayController displayController,
@@ -274,7 +273,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = boundsHandler;
- mMenuActivityController = menuActivityController;
+ mPipMenuController = pipMenuController;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = surfaceTransactionHelper;
@@ -501,9 +500,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mOnDisplayIdChangeCallback.accept(info.displayId);
}
- if (mMenuActivityController != null) {
- mMenuActivityController.onTaskAppeared();
- }
+ mPipMenuController.attach(leash);
+
if (mShouldIgnoreEnteringPipTransition) {
final Rect destinationBounds = mPipBoundsState.getBounds();
@@ -674,9 +672,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPictureInPictureParams = null;
mState = State.UNDEFINED;
mPipUiEventLoggerLogger.setTaskInfo(null);
- if (mMenuActivityController != null) {
- mMenuActivityController.onTaskVanished();
- }
+ mPipMenuController.detach();
}
@Override
@@ -819,6 +815,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
+ /**
+ * Animates resizing of the pinned stack given the duration and start bounds.
+ * This is used when the starting bounds is not the current PiP bounds.
+ */
+ public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
+ Consumer<Rect> updateBoundsCallback) {
+ if (mShouldDeferEnteringPip) {
+ Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
+ return;
+ }
+ scheduleAnimateResizePip(fromBounds, toBounds, null /* sourceHintRect */,
+ TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, duration, updateBoundsCallback);
+ }
+
private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
int durationMs, Consumer<Rect> updateBoundsCallback) {
@@ -956,9 +966,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
.round(tx, mLeash, mState.isInPip());
- if (mMenuActivityController != null && mMenuActivityController.isMenuVisible()) {
+ if (mPipMenuController.isMenuVisible()) {
runOnMainHandler(() ->
- mMenuActivityController.resizePipMenu(mLeash, tx, destinationBounds));
+ mPipMenuController.resizePipMenu(mLeash, tx, destinationBounds));
} else {
tx.apply();
}
@@ -982,9 +992,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds);
- if (mMenuActivityController != null && mMenuActivityController.isMenuVisible()) {
+ if (mPipMenuController.isMenuVisible()) {
runOnMainHandler(() ->
- mMenuActivityController.movePipMenu(mLeash, tx, destinationBounds));
+ mPipMenuController.movePipMenu(mLeash, tx, destinationBounds));
} else {
tx.apply();
}
@@ -1001,8 +1011,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
removePipImmediately();
return;
- } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA
- && mMenuActivityController != null) {
+ } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
// TODO: Synchronize this correctly in #applyEnterPipSyncTransaction
finishResizeForMenu(destinationBounds);
return;
@@ -1015,13 +1024,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void finishResizeForMenu(Rect destinationBounds) {
- if (mMenuActivityController == null) {
- if (DEBUG) Log.d(TAG, "mMenuActivityController is null");
- return;
- }
runOnMainHandler(() -> {
- mMenuActivityController.movePipMenu(null, null, destinationBounds);
- mMenuActivityController.updateMenuBounds(destinationBounds);
+ mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.updateMenuBounds(destinationBounds);
});
}
@@ -1083,8 +1088,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Log.w(TAG, "Abort animation, invalid leash");
return;
}
+ Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
+ ? mPipBoundsState.getBounds() : currentBounds;
mPipAnimationController
- .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect, direction)
+ .getAnimator(mLeash, baseBounds, currentBounds, destinationBounds, sourceHintRect,
+ direction)
.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index d4217553ef2d..5db8f3d7ef40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -18,12 +18,6 @@ package com.android.wm.shell.pip.phone;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.annotation.Nullable;
@@ -33,7 +27,6 @@ import android.app.RemoteAction;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Debug;
@@ -44,27 +37,26 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.pip.PipMenuController;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
- * Manages the PiP menu activity which can show menu options or a scrim.
+ * Manages the PiP menu view which can show menu options or a scrim.
*
* The current media session provides actions whenever there are no valid actions provided by the
* current PiP activity. Otherwise, those actions always take precedence.
*/
-public class PipMenuActivityController {
+public class PhonePipMenuController implements PipMenuController {
private static final String TAG = "PipMenuActController";
- private static final String MENU_WINDOW_TITLE = "PipMenuView";
private static final boolean DEBUG = false;
public static final int MENU_STATE_NONE = 0;
@@ -124,7 +116,7 @@ public class PipMenuActivityController {
}
};
- public PipMenuActivityController(Context context,
+ public PhonePipMenuController(Context context,
PipMediaController mediaController, SystemWindows systemWindows) {
mContext = context;
mMediaController = mediaController;
@@ -138,20 +130,22 @@ public class PipMenuActivityController {
/**
* Attach the menu when the PiP task first appears.
*/
- public void onTaskAppeared() {
+ @Override
+ public void attach(SurfaceControl leash) {
attachPipMenuView();
}
/**
* Detach the menu when the PiP task is gone.
*/
- public void onTaskVanished() {
+ @Override
+ public void detach() {
hideMenu();
detachPipMenuView();
}
- public void onPinnedStackAnimationEnded() {
+ void onPinnedStackAnimationEnded() {
if (isMenuVisible()) {
mPipMenuView.onPipAnimationEnded();
}
@@ -163,7 +157,9 @@ public class PipMenuActivityController {
detachPipMenuView();
}
mPipMenuView = new PipMenuView(mContext, this);
- mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(0, 0), 0, SHELL_ROOT_LAYER_PIP);
+ mSystemWindows.addView(mPipMenuView,
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ 0, SHELL_ROOT_LAYER_PIP);
}
private void detachPipMenuView() {
@@ -181,9 +177,11 @@ public class PipMenuActivityController {
* Updates the layout parameters of the menu.
* @param destinationBounds New Menu bounds.
*/
+ @Override
public void updateMenuBounds(Rect destinationBounds) {
mSystemWindows.updateViewLayout(mPipMenuView,
- getPipMenuLayoutParams(destinationBounds.width(), destinationBounds.height()));
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, destinationBounds.width(),
+ destinationBounds.height()));
}
/**
@@ -206,6 +204,16 @@ public class PipMenuActivityController {
}
/**
+ * When other components requests the menu controller directly to show the menu, we must
+ * first fire off the request to the other listeners who will then propagate the call
+ * back to the controller with the right parameters.
+ */
+ @Override
+ public void showMenu() {
+ mListeners.forEach(Listener::onPipShowMenu);
+ }
+
+ /**
* Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
* upon PiP window transition is finished.
*/
@@ -250,6 +258,7 @@ public class PipMenuActivityController {
/**
* Move the PiP menu, which does a translation and possibly a scale transformation.
*/
+ @Override
public void movePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
@@ -290,6 +299,7 @@ public class PipMenuActivityController {
/**
* Does an immediate window crop of the PiP menu.
*/
+ @Override
public void resizePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
@@ -391,8 +401,9 @@ public class PipMenuActivityController {
}
/**
- * Sets the menu actions to the actions provided by the current PiP activity.
+ * Sets the menu actions to the actions provided by the current PiP menu.
*/
+ @Override
public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
mAppActions = appActions;
updateMenuActions();
@@ -406,10 +417,6 @@ public class PipMenuActivityController {
mListeners.forEach(Listener::onPipDismiss);
}
- void onPipShowMenu() {
- mListeners.forEach(Listener::onPipShowMenu);
- }
-
/**
* @return the best set of actions to show in the PiP menu.
*/
@@ -421,21 +428,6 @@ public class PipMenuActivityController {
}
/**
- * Returns a default LayoutParams for the PIP Menu.
- * @param width the PIP stack width.
- * @param height the PIP stack height.
- */
- public static WindowManager.LayoutParams getPipMenuLayoutParams(int width, int height) {
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
- TYPE_APPLICATION_OVERLAY,
- FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
- PixelFormat.TRANSLUCENT);
- lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
- lp.setTitle(MENU_WINDOW_TITLE);
- return lp;
- }
-
- /**
* Updates the PiP menu with the best set of actions provided.
*/
private void updateMenuActions() {
@@ -521,7 +513,7 @@ public class PipMenuActivityController {
}
}
- public void dump(PrintWriter pw, String prefix) {
+ void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mMenuState=" + mMenuState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index f153aa5a1beb..7194fc70025c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -20,15 +20,18 @@ import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
-import android.os.Handler;
import android.os.RemoteException;
import android.view.MagnificationSpec;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import androidx.annotation.BinderThread;
+
import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -40,8 +43,7 @@ import java.util.List;
* Expose the touch actions to accessibility as if this object were a window with a single view.
* That pseudo-view exposes all of the actions this object can perform.
*/
-public class PipAccessibilityInteractionConnection
- extends IAccessibilityInteractionConnection.Stub {
+public class PipAccessibilityInteractionConnection {
public interface AccessibilityCallbacks {
void onAccessibilityShowMenu();
@@ -50,14 +52,15 @@ public class PipAccessibilityInteractionConnection
private static final long ACCESSIBILITY_NODE_ID = 1;
private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList;
- private Context mContext;
- private Handler mHandler;
+ private final Context mContext;
+ private final ShellExecutor mShellMainExcutor;
private final @NonNull PipBoundsState mPipBoundsState;
- private PipMotionHelper mMotionHelper;
- private PipTaskOrganizer mTaskOrganizer;
- private PipSnapAlgorithm mSnapAlgorithm;
- private Runnable mUpdateMovementBoundCallback;
- private AccessibilityCallbacks mCallbacks;
+ private final PipMotionHelper mMotionHelper;
+ private final PipTaskOrganizer mTaskOrganizer;
+ private final PipSnapAlgorithm mSnapAlgorithm;
+ private final Runnable mUpdateMovementBoundCallback;
+ private final AccessibilityCallbacks mCallbacks;
+ private final IAccessibilityInteractionConnection mConnectionImpl;
private final Rect mNormalBounds = new Rect();
private final Rect mExpandedBounds = new Rect();
@@ -69,19 +72,23 @@ public class PipAccessibilityInteractionConnection
@NonNull PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer taskOrganizer, PipSnapAlgorithm snapAlgorithm,
AccessibilityCallbacks callbacks, Runnable updateMovementBoundCallback,
- Handler handler) {
+ ShellExecutor shellMainExcutor) {
mContext = context;
- mHandler = handler;
+ mShellMainExcutor = shellMainExcutor;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mTaskOrganizer = taskOrganizer;
mSnapAlgorithm = snapAlgorithm;
mUpdateMovementBoundCallback = updateMovementBoundCallback;
mCallbacks = callbacks;
+ mConnectionImpl = new PipAccessibilityInteractionConnectionImpl();
+ }
+
+ public void register(AccessibilityManager am) {
+ am.setPictureInPictureActionReplacingConnection(mConnectionImpl);
}
- @Override
- public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
+ private void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) {
@@ -94,8 +101,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void performAccessibilityAction(long accessibilityNodeId, int action,
+ private void performAccessibilityAction(long accessibilityNodeId, int action,
Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
@@ -115,9 +121,7 @@ public class PipAccessibilityInteractionConnection
} else {
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK:
- mHandler.post(() -> {
- mCallbacks.onAccessibilityShowMenu();
- });
+ mCallbacks.onAccessibilityShowMenu();
result = true;
break;
case AccessibilityNodeInfo.ACTION_DISMISS:
@@ -172,8 +176,7 @@ public class PipAccessibilityInteractionConnection
});
}
- @Override
- public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ private void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
String viewId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
@@ -185,8 +188,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+ private void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
@@ -198,8 +200,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
+ private void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
// We have no view that can take focus
@@ -210,8 +211,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
+ private void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
// We have no view that can take focus
@@ -222,16 +222,6 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void clearAccessibilityFocus() {
- // We should not be here.
- }
-
- @Override
- public void notifyOutsideTouch() {
- // Do nothing.
- }
-
/**
* Update the normal and expanded bounds so they can be used for Resize.
*/
@@ -271,4 +261,95 @@ public class PipAccessibilityInteractionConnection
mAccessibilityNodeInfoList.add(info);
return mAccessibilityNodeInfoList;
}
+
+ @BinderThread
+ private class PipAccessibilityInteractionConnectionImpl
+ extends IAccessibilityInteractionConnection.Stub {
+ @Override
+ public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
+ Region bounds, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec,
+ Bundle arguments) throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this
+ .findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, bounds,
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec, arguments);
+ });
+ }
+
+ @Override
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
+ Region bounds, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.findAccessibilityNodeInfosByViewId(
+ accessibilityNodeId, viewId, bounds, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid, spec);
+ });
+ }
+
+ @Override
+ public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+ Region bounds, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.findAccessibilityNodeInfosByText(
+ accessibilityNodeId, text, bounds, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid, spec);
+ });
+ }
+
+ @Override
+ public void findFocus(long accessibilityNodeId, int focusType, Region bounds,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.findFocus(accessibilityNodeId, focusType,
+ bounds, interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
+ });
+ }
+
+ @Override
+ public void focusSearch(long accessibilityNodeId, int direction, Region bounds,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.focusSearch(accessibilityNodeId,
+ direction,
+ bounds, interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
+ });
+ }
+
+ @Override
+ public void performAccessibilityAction(long accessibilityNodeId, int action,
+ Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid) throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.performAccessibilityAction(
+ accessibilityNodeId, action, arguments, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid);
+ });
+ }
+
+ @Override
+ public void clearAccessibilityFocus() throws RemoteException {
+ // Do nothing
+ }
+
+ @Override
+ public void notifyOutsideTouch() throws RemoteException {
+ // Do nothing
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 598b5d9b5d30..3234ef6ccf66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -41,7 +41,6 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.view.DisplayInfo;
-import android.view.IPinnedStackController;
import android.view.WindowManagerGlobal;
import android.window.WindowContainerTransaction;
@@ -52,7 +51,6 @@ import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.PipInputConsumer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -92,7 +90,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private boolean mIsInFixedRotation;
private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
- protected PipMenuActivityController mMenuController;
+ protected PhonePipMenuController mMenuController;
protected PipTaskOrganizer mPipTaskOrganizer;
protected PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener =
new PipControllerPinnedStackListener();
@@ -163,63 +161,50 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private class PipControllerPinnedStackListener extends
PinnedStackListenerForwarder.PinnedStackListener {
@Override
- public void onListenerRegistered(IPinnedStackController controller) {
- mMainExecutor.execute(() -> mTouchHandler.setPinnedStackController(controller));
- }
-
- @Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mMainExecutor.execute(() -> {
- mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
- });
+ mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
+ mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mMainExecutor.execute(() -> updateMovementBounds(null /* toBounds */,
+ updateMovementBounds(null /* toBounds */,
false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */));
+ null /* windowContainerTransaction */);
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- mMainExecutor.execute(() -> mMenuController.setAppActions(actions));
+ mMenuController.setAppActions(actions);
}
@Override
public void onActivityHidden(ComponentName componentName) {
- mMainExecutor.execute(() -> {
- if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
- // The activity was removed, we don't want to restore to the reentry state
- // saved for this component anymore.
- mPipBoundsState.setLastPipComponentName(null);
- }
- });
+ if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
+ // The activity was removed, we don't want to restore to the reentry state
+ // saved for this component anymore.
+ mPipBoundsState.setLastPipComponentName(null);
+ }
}
@Override
public void onDisplayInfoChanged(DisplayInfo displayInfo) {
- mMainExecutor.execute(() -> mPipBoundsState.setDisplayInfo(displayInfo));
+ mPipBoundsState.setDisplayInfo(displayInfo);
}
@Override
public void onConfigurationChanged() {
- mMainExecutor.execute(() -> {
- mPipBoundsAlgorithm.onConfigurationChanged(mContext);
- mTouchHandler.onConfigurationChanged();
- mPipBoundsState.onConfigurationChanged();
- });
+ mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ mTouchHandler.onConfigurationChanged();
+ mPipBoundsState.onConfigurationChanged();
}
@Override
public void onAspectRatioChanged(float aspectRatio) {
// TODO(b/169373982): Remove this callback as it is redundant with PipTaskOrg params
// change.
- mMainExecutor.execute(() -> {
- mPipBoundsState.setAspectRatio(aspectRatio);
- mTouchHandler.onAspectRatioChanged();
- });
+ mPipBoundsState.setAspectRatio(aspectRatio);
+ mTouchHandler.onAspectRatioChanged();
}
}
@@ -229,7 +214,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipMediaController pipMediaController,
- PipMenuActivityController pipMenuActivityController,
+ PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -250,7 +235,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipTaskOrganizer = pipTaskOrganizer;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
- mMenuController = pipMenuActivityController;
+ mMenuController = phonePipMenuController;
mTouchHandler = pipTouchHandler;
mAppOpsListener = pipAppOpsListener;
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
@@ -632,7 +617,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
public static PipController create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
- PipMenuActivityController pipMenuActivityController, PipTaskOrganizer pipTaskOrganizer,
+ PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -641,7 +626,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer,
+ pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 87ddb181dcce..0c64c8ca9b64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.wm.shell.common;
+package com.android.wm.shell.pip.phone;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
-import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import android.os.Binder;
import android.os.IBinder;
@@ -30,7 +28,6 @@ import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
-import android.view.WindowManagerGlobal;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 6e3cd8e9aa09..2e10fc93cafb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -23,9 +23,9 @@ import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTR
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -105,7 +105,7 @@ public class PipMenuView extends FrameLayout {
private int mBetweenActionPaddingLand;
private AnimatorSet mMenuContainerAnimator;
- private PipMenuActivityController mController;
+ private PhonePipMenuController mController;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -127,7 +127,7 @@ public class PipMenuView extends FrameLayout {
protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
- public PipMenuView(Context context, PipMenuActivityController controller) {
+ public PipMenuView(Context context, PhonePipMenuController controller) {
super(context, null, 0);
mContext = context;
mController = controller;
@@ -182,7 +182,7 @@ public class PipMenuView extends FrameLayout {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
- mController.onPipShowMenu();
+ mController.showMenu();
}
return super.performAccessibilityAction(host, action, args);
}
@@ -475,7 +475,7 @@ public class PipMenuView extends FrameLayout {
final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(settingsIntent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 903f7d773896..e32d3b955081 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -70,7 +70,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private final PipTaskOrganizer mPipTaskOrganizer;
private @NonNull PipBoundsState mPipBoundsState;
- private PipMenuActivityController mMenuController;
+ private PhonePipMenuController mMenuController;
private PipSnapAlgorithm mSnapAlgorithm;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -162,7 +162,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
};
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
- PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
+ PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
new file mode 100644
index 000000000000..28cbe35745a9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 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.pip.phone;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * Helper class to calculate the new size given two-fingers pinch to resize.
+ */
+public class PipPinchResizingAlgorithm {
+ private static final Rect TMP_RECT = new Rect();
+ /**
+ * Given inputs and requirements and current PiP bounds, return the new size.
+ *
+ * @param x0 x-coordinate of the primary input.
+ * @param y0 y-coordinate of the primary input.
+ * @param x1 x-coordinate of the secondary input.
+ * @param y1 y-coordinate of the secondary input.
+ * @param downx0 x-coordinate of the original down point of the primary input.
+ * @param downy0 y-coordinate of the original down ponit of the primary input.
+ * @param downx1 x-coordinate of the original down point of the secondary input.
+ * @param downy1 y-coordinate of the original down point of the secondary input.
+ * @param currentPipBounds current PiP bounds.
+ * @param minVisibleWidth minimum visible width.
+ * @param minVisibleHeight minimum visible height.
+ * @param maxSize max size.
+ * @return The new resized PiP bounds, sharing the same center.
+ */
+ public static Rect pinchResize(float x0, float y0, float x1, float y1,
+ float downx0, float downy0, float downx1, float downy1, Rect currentPipBounds,
+ int minVisibleWidth, int minVisibleHeight, Point maxSize) {
+
+ int width = currentPipBounds.width();
+ int height = currentPipBounds.height();
+ int left = currentPipBounds.left;
+ int top = currentPipBounds.top;
+ int right = currentPipBounds.right;
+ int bottom = currentPipBounds.bottom;
+ final float aspect = (float) width / (float) height;
+ final int widthDelta = Math.round(Math.abs(x0 - x1) - Math.abs(downx0 - downx1));
+ final int heightDelta = Math.round(Math.abs(y0 - y1) - Math.abs(downy0 - downy1));
+
+ width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x));
+ height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y));
+
+ // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
+ // drag axis. What ever is producing the bigger rectangle will be chosen.
+ int width1;
+ int width2;
+ int height1;
+ int height2;
+ if (aspect > 1.0f) {
+ // Assuming that the width is our target we calculate the height.
+ width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width));
+ height1 = Math.round((float) width1 / aspect);
+ if (height1 < minVisibleHeight) {
+ // If the resulting height is too small we adjust to the minimal size.
+ height1 = minVisibleHeight;
+ width1 = Math.max(minVisibleWidth,
+ Math.min(maxSize.x, Math.round((float) height1 * aspect)));
+ }
+ // Assuming that the height is our target we calculate the width.
+ height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height));
+ width2 = Math.round((float) height2 * aspect);
+ if (width2 < minVisibleWidth) {
+ // If the resulting width is too small we adjust to the minimal size.
+ width2 = minVisibleWidth;
+ height2 = Math.max(minVisibleHeight,
+ Math.min(maxSize.y, Math.round((float) width2 / aspect)));
+ }
+ } else {
+ // Assuming that the width is our target we calculate the height.
+ width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width));
+ height1 = Math.round((float) width1 * aspect);
+ if (height1 < minVisibleHeight) {
+ // If the resulting height is too small we adjust to the minimal size.
+ height1 = minVisibleHeight;
+ width1 = Math.max(minVisibleWidth,
+ Math.min(maxSize.x, Math.round((float) height1 / aspect)));
+ }
+ // Assuming that the height is our target we calculate the width.
+ height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height));
+ width2 = Math.round((float) height2 / aspect);
+ if (width2 < minVisibleWidth) {
+ // If the resulting width is too small we adjust to the minimal size.
+ width2 = minVisibleWidth;
+ height2 = Math.max(minVisibleHeight,
+ Math.min(maxSize.y, Math.round((float) width2 * aspect)));
+ }
+ }
+
+ // Use the bigger of the two rectangles if the major change was positive, otherwise
+ // do the opposite.
+ final boolean grows = width > (right - left) || height > (bottom - top);
+ if (grows == (width1 * height1 > width2 * height2)) {
+ width = width1;
+ height = height1;
+ } else {
+ width = width2;
+ height = height2;
+ }
+
+ TMP_RECT.set(currentPipBounds.centerX() - width / 2,
+ currentPipBounds.centerY() - height / 2,
+ currentPipBounds.centerX() + width / 2,
+ currentPipBounds.centerY() + height / 2);
+ return TMP_RECT;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index e5a49c430d82..88a11689b90b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -39,7 +39,6 @@ import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
import androidx.annotation.VisibleForTesting;
@@ -62,19 +61,21 @@ import java.util.function.Function;
public class PipResizeGestureHandler {
private static final String TAG = "PipResizeGestureHandler";
- private static final float PINCH_THRESHOLD = 0.05f;
- private static final float STARTING_SCALE_FACTOR = 1.0f;
+ private static final int PINCH_RESIZE_SNAP_DURATION = 250;
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipMotionHelper mMotionHelper;
private final PipBoundsState mPipBoundsState;
+ private final PipTaskOrganizer mPipTaskOrganizer;
+ private final PhonePipMenuController mPhonePipMenuController;
+ private final PipUiEventLogger mPipUiEventLogger;
private final int mDisplayId;
private final Executor mMainExecutor;
- private final ScaleGestureDetector mScaleGestureDetector;
private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
+ private final PointF mDownSecondaryPoint = new PointF();
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
private final Rect mLastResizeBounds = new Rect();
@@ -88,6 +89,7 @@ public class PipResizeGestureHandler {
private final Rect mDisplayBounds = new Rect();
private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
+ private final Handler mHandler;
private int mDelta;
private float mTouchSlop;
@@ -96,15 +98,17 @@ public class PipResizeGestureHandler {
private boolean mIsEnabled;
private boolean mEnablePinchResize;
private boolean mIsSysUiStateValid;
+ // For drag-resize
private boolean mThresholdCrossed;
+ // For pinch-resize
+ private boolean mThresholdCrossed0;
+ private boolean mThresholdCrossed1;
private boolean mUsingPinchToZoom = false;
- private float mScaleFactor = STARTING_SCALE_FACTOR;
+ int mFirstIndex = -1;
+ int mSecondIndex = -1;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
- private PipTaskOrganizer mPipTaskOrganizer;
- private PipMenuActivityController mPipMenuActivityController;
- private PipUiEventLogger mPipUiEventLogger;
private int mCtrlType;
@@ -112,7 +116,7 @@ public class PipResizeGestureHandler {
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger,
- PipMenuActivityController menuActivityController) {
+ PhonePipMenuController menuActivityController) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = context.getMainExecutor();
@@ -122,68 +126,13 @@ public class PipResizeGestureHandler {
mPipTaskOrganizer = pipTaskOrganizer;
mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
- mPipMenuActivityController = menuActivityController;
+ mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
+ mHandler = new Handler(Looper.getMainLooper());
context.getDisplay().getRealSize(mMaxSize);
reloadResources();
- mScaleGestureDetector = new ScaleGestureDetector(context,
- new ScaleGestureDetector.OnScaleGestureListener() {
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- mScaleFactor *= detector.getScaleFactor();
-
- if (!mThresholdCrossed
- && (mScaleFactor > (STARTING_SCALE_FACTOR + PINCH_THRESHOLD)
- || mScaleFactor < (STARTING_SCALE_FACTOR - PINCH_THRESHOLD))) {
- mThresholdCrossed = true;
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- int height = Math.min(mMaxSize.y, Math.max(mMinSize.y,
- (int) (mScaleFactor * mLastDownBounds.height())));
- int width = Math.min(mMaxSize.x, Math.max(mMinSize.x,
- (int) (mScaleFactor * mLastDownBounds.width())));
- int top, bottom, left, right;
-
- if ((mCtrlType & CTRL_TOP) != 0) {
- top = mLastDownBounds.bottom - height;
- bottom = mLastDownBounds.bottom;
- } else {
- top = mLastDownBounds.top;
- bottom = mLastDownBounds.top + height;
- }
-
- if ((mCtrlType & CTRL_LEFT) != 0) {
- left = mLastDownBounds.right - width;
- right = mLastDownBounds.right;
- } else {
- left = mLastDownBounds.left;
- right = mLastDownBounds.left + width;
- }
-
- mLastResizeBounds.set(left, top, right, bottom);
- mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds,
- mLastResizeBounds,
- null);
- }
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- setCtrlTypeForPinchToZoom();
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mScaleFactor = STARTING_SCALE_FACTOR;
- finishResize();
- }
- });
-
mEnablePinchResize = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_PINCH_RESIZE,
@@ -274,7 +223,7 @@ public class PipResizeGestureHandler {
if (ev instanceof MotionEvent) {
if (mUsingPinchToZoom) {
- mScaleGestureDetector.onTouchEvent((MotionEvent) ev);
+ onPinchResize((MotionEvent) ev);
} else {
onDragCornerResize((MotionEvent) ev);
}
@@ -282,6 +231,13 @@ public class PipResizeGestureHandler {
}
/**
+ * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize.
+ */
+ public boolean hasOngoingGesture() {
+ return mCtrlType != CTRL_NONE || mUsingPinchToZoom;
+ }
+
+ /**
* Check whether the current x,y coordinate is within the region in which drag-resize should
* start.
* This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
@@ -295,7 +251,7 @@ public class PipResizeGestureHandler {
* |_|_|_________|_|_|
* |_|_| |_|_|
*/
- public boolean isWithinTouchRegion(int x, int y) {
+ public boolean isWithinDragResizeRegion(int x, int y) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
if (currentPipBounds == null) {
return false;
@@ -327,15 +283,14 @@ public class PipResizeGestureHandler {
if (isInValidSysUiState()) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- // Always pass the DOWN event to the ScaleGestureDetector
- mScaleGestureDetector.onTouchEvent(ev);
- if (isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) {
+ if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
return true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
mUsingPinchToZoom = true;
return true;
}
@@ -348,33 +303,11 @@ public class PipResizeGestureHandler {
return false;
}
- private void setCtrlTypeForPinchToZoom() {
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- mLastDownBounds.set(mPipBoundsState.getBounds());
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
-
- if (currentPipBounds.left == mDisplayBounds.left) {
- mCtrlType |= CTRL_RIGHT;
- } else {
- mCtrlType |= CTRL_LEFT;
- }
-
- if (currentPipBounds.top > mDisplayBounds.top + mDisplayBounds.height()) {
- mCtrlType |= CTRL_TOP;
- } else {
- mCtrlType |= CTRL_BOTTOM;
- }
- }
-
private void setCtrlType(int x, int y) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
+
mDisplayBounds.set(movementBounds.left,
movementBounds.top,
movementBounds.right + currentPipBounds.width(),
@@ -408,6 +341,78 @@ public class PipResizeGestureHandler {
return mIsSysUiStateValid;
}
+ private void onPinchResize(MotionEvent ev) {
+ int action = ev.getActionMasked();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mFirstIndex = -1;
+ mSecondIndex = -1;
+ finishResize();
+ }
+
+ if (ev.getPointerCount() != 2) {
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mFirstIndex == -1 && mSecondIndex == -1) {
+ mFirstIndex = 0;
+ mSecondIndex = 1;
+ mLastResizeBounds.setEmpty();
+ mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
+ mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
+
+ mLastResizeBounds.setEmpty();
+ mLastDownBounds.set(mPipBoundsState.getBounds());
+ }
+ }
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (mFirstIndex == -1 || mSecondIndex == -1) {
+ return;
+ }
+
+ float x0 = ev.getRawX(mFirstIndex);
+ float y0 = ev.getRawY(mFirstIndex);
+ float x1 = ev.getRawX(mSecondIndex);
+ float y1 = ev.getRawY(mSecondIndex);
+
+ double hypot0 = Math.hypot(x0 - mDownPoint.x, y0 - mDownPoint.y);
+ double hypot1 = Math.hypot(x1 - mDownSecondaryPoint.x, y1 - mDownSecondaryPoint.y);
+ // Capture inputs
+ if (hypot0 > mTouchSlop && !mThresholdCrossed0) {
+ mInputMonitor.pilferPointers();
+ mThresholdCrossed0 = true;
+ // Reset the down to begin resizing from this point
+ mDownPoint.set(x0, y0);
+ }
+ if (hypot1 > mTouchSlop && !mThresholdCrossed1) {
+ mInputMonitor.pilferPointers();
+ mThresholdCrossed1 = true;
+ // Reset the down to begin resizing from this point
+ mDownSecondaryPoint.set(x1, y1);
+ }
+ if (mThresholdCrossed0 || mThresholdCrossed1) {
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
+ }
+
+ x0 = mThresholdCrossed0 ? x0 : mDownPoint.x;
+ y0 = mThresholdCrossed0 ? y0 : mDownPoint.y;
+ x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x;
+ y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y;
+
+ final Rect currentPipBounds = mPipBoundsState.getBounds();
+ mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1,
+ mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y,
+ currentPipBounds, mMinSize.x, mMinSize.y, mMaxSize));
+
+ mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
+ null);
+ }
+ }
+ }
+
private void onDragCornerResize(MotionEvent ev) {
int action = ev.getActionMasked();
float x = ev.getX();
@@ -415,15 +420,15 @@ public class PipResizeGestureHandler {
if (action == MotionEvent.ACTION_DOWN) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
mLastResizeBounds.setEmpty();
- mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y);
+ mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
if (mAllowGesture) {
setCtrlType((int) x, (int) y);
mDownPoint.set(x, y);
mLastDownBounds.set(mPipBoundsState.getBounds());
}
if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY())
- && mPipMenuActivityController.isMenuVisible()) {
- mPipMenuActivityController.hideMenu();
+ && mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
}
} else if (mAllowGesture) {
@@ -442,9 +447,9 @@ public class PipResizeGestureHandler {
mInputMonitor.pilferPointers();
}
if (mThresholdCrossed) {
- if (mPipMenuActivityController.isMenuVisible()) {
- mPipMenuActivityController.hideMenuWithoutResize();
- mPipMenuActivityController.hideMenu();
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenuWithoutResize();
+ mPhonePipMenuController.hideMenu();
}
final Rect currentPipBounds = mPipBoundsState.getBounds();
mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
@@ -468,15 +473,30 @@ public class PipResizeGestureHandler {
private void finishResize() {
if (!mLastResizeBounds.isEmpty()) {
- mUserResizeBounds.set(mLastResizeBounds);
- mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
- (Rect bounds) -> {
- new Handler(Looper.getMainLooper()).post(() -> {
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
- resetState();
+ final Runnable callback = () -> {
+ mUserResizeBounds.set(mLastResizeBounds);
+ mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
+
+ // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
+ // position correctly. Drag-resize does not need to move, so just finalize resize.
+ if (mUsingPinchToZoom) {
+ final Rect startBounds = new Rect(mLastResizeBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
+ mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
+ mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
+ PINCH_RESIZE_SNAP_DURATION,
+ (Rect rect) -> {
+ mHandler.post(callback);
});
- });
+ } else {
+ mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
+ (Rect bounds) -> {
+ mHandler.post(callback);
+ });
+ }
mPipUiEventLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a78c4ecdb39f..9281f58f522f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -18,9 +18,9 @@ package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -31,11 +31,8 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.RemoteException;
import android.provider.DeviceConfig;
-import android.util.Log;
import android.util.Size;
-import android.view.IPinnedStackController;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -47,6 +44,7 @@ import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -75,10 +73,9 @@ public class PipTouchHandler {
private final PipDismissTargetHandler mPipDismissTargetHandler;
private PipResizeGestureHandler mPipResizeGestureHandler;
- private IPinnedStackController mPinnedStackController;
private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener;
- private final PipMenuActivityController mMenuController;
+ private final PhonePipMenuController mMenuController;
private final AccessibilityManager mAccessibilityManager;
private boolean mShowPipMenuOnAnimationEnd = false;
@@ -125,7 +122,7 @@ public class PipTouchHandler {
/**
* A listener for the PIP menu activity.
*/
- private class PipMenuListener implements PipMenuActivityController.Listener {
+ private class PipMenuListener implements PhonePipMenuController.Listener {
@Override
public void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback) {
setMenuState(menuState, resize, callback);
@@ -152,12 +149,13 @@ public class PipTouchHandler {
@SuppressLint("InflateParams")
public PipTouchHandler(Context context,
- PipMenuActivityController menuController,
+ PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
FloatingContentCoordinator floatingContentCoordinator,
- PipUiEventLogger pipUiEventLogger) {
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor shellMainExecutor) {
// Initialize the Pip input consumer
mContext = context;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -188,7 +186,7 @@ public class PipTouchHandler {
mFloatingContentCoordinator = floatingContentCoordinator;
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
- this::onAccessibilityShowMenu, this::updateMovementBounds, mHandler);
+ this::onAccessibilityShowMenu, this::updateMovementBounds, shellMainExecutor);
mPipUiEventLogger = pipUiEventLogger;
@@ -436,8 +434,11 @@ public class PipTouchHandler {
* TODO Add appropriate description
*/
public void onRegistrationChanged(boolean isRegistered) {
- mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
- ? mConnection : null);
+ if (isRegistered) {
+ mConnection.register(mAccessibilityManager);
+ } else {
+ mAccessibilityManager.setPictureInPictureActionReplacingConnection(null);
+ }
if (!isRegistered && mTouchState.isUserInteracting()) {
// If the input consumer is unregistered while the user is interacting, then we may not
// get the final TOUCH_UP event, so clean up the dismiss target as well
@@ -459,10 +460,6 @@ public class PipTouchHandler {
if (!(inputEvent instanceof MotionEvent)) {
return true;
}
- // Skip touch handling until we are bound to the controller
- if (mPinnedStackController == null) {
- return true;
- }
MotionEvent ev = (MotionEvent) inputEvent;
if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
@@ -473,6 +470,11 @@ public class PipTouchHandler {
return true;
}
+ if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ return true;
+ }
+
if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
&& mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
// If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
@@ -586,13 +588,6 @@ public class PipTouchHandler {
}
/**
- * Sets the controller to update the system of changes from user interaction.
- */
- void setPinnedStackController(IPinnedStackController controller) {
- mPinnedStackController = controller;
- }
-
- /**
* Sets the menu visibility.
*/
private void setMenuState(int menuState, boolean resize, Runnable callback) {
@@ -620,13 +615,9 @@ public class PipTouchHandler {
// bounds which are now stale. In such a case we defer the animation to the
// normal bounds until after the next onMovementBoundsChanged() call to get the
// bounds in the new orientation
- try {
- int displayRotation = mPinnedStackController.getDisplayRotation();
- if (mDisplayRotation != displayRotation) {
- mDeferResizeToNormalBoundsUntilRotation = displayRotation;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Could not get display rotation from controller");
+ int displayRotation = mContext.getDisplay().getRotation();
+ if (mDisplayRotation != displayRotation) {
+ mDeferResizeToNormalBoundsUntilRotation = displayRotation;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index 217150770084..5f2327ce98d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone;
import android.graphics.PointF;
import android.os.Handler;
import android.util.Log;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -64,6 +65,7 @@ public class PipTouchState {
private boolean mStartedDragging = false;
private boolean mAllowDraggingOffscreen = false;
private int mActivePointerId;
+ private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
public PipTouchState(ViewConfiguration viewConfig, Handler handler,
Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) {
@@ -81,12 +83,14 @@ public class PipTouchState {
mIsDragging = false;
mStartedDragging = false;
mIsUserInteracting = false;
+ mLastTouchDisplayId = Display.INVALID_DISPLAY;
}
/**
* Processes a given touch event and updates the state.
*/
public void onTouchEvent(MotionEvent ev) {
+ mLastTouchDisplayId = ev.getDisplayId();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (!mAllowTouches) {
@@ -266,6 +270,13 @@ public class PipTouchState {
}
/**
+ * @return Display ID of the last touch event.
+ */
+ public int getLastTouchDisplayId() {
+ return mLastTouchDisplayId;
+ }
+
+ /**
* Sets whether touching is currently allowed.
*/
public void setAllowTouches(boolean allowTouches) {
@@ -378,6 +389,7 @@ public class PipTouchState {
pw.println(prefix + TAG);
pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
+ pw.println(innerPrefix + "mLastTouchDisplayId=" + mLastTouchDisplayId);
pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
index 56e97b91c9d2..763370bec1c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
@@ -36,7 +36,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
@@ -59,13 +58,14 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
- private static final String TAG = "PipController";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String TAG = "TvPipController";
+ static final boolean DEBUG = false;
/**
* Unknown or invalid state
@@ -111,15 +111,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
+ private final TvPipMenuController mTvPipMenuController;
private IActivityTaskManager mActivityTaskManager;
private int mState = STATE_NO_PIP;
private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
private final Handler mHandler = new Handler();
private List<Listener> mListeners = new ArrayList<>();
- private Rect mPipBounds;
- private Rect mDefaultPipBounds = new Rect();
- private Rect mMenuModePipBounds;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
private int mPipTaskId = TASK_ID_NO_PIP;
private int mPinnedStackId = INVALID_STACK_ID;
@@ -181,46 +179,33 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mHandler.post(() -> {
- mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- if (mState == STATE_PIP) {
- if (mImeVisible != imeVisible) {
- if (imeVisible) {
- // Save the IME height adjustment, and offset to not occlude the IME
- mPipBounds.offset(0, -imeHeight);
- mImeHeightAdjustment = imeHeight;
- } else {
- // Apply the inverse adjustment when the IME is hidden
- mPipBounds.offset(0, mImeHeightAdjustment);
- }
- mImeVisible = imeVisible;
- resizePinnedStack(STATE_PIP);
+ mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
+ if (mState == STATE_PIP) {
+ if (mImeVisible != imeVisible) {
+ if (imeVisible) {
+ // Save the IME height adjustment, and offset to not occlude the IME
+ mPipBoundsState.getNormalBounds().offset(0, -imeHeight);
+ mImeHeightAdjustment = imeHeight;
+ } else {
+ // Apply the inverse adjustment when the IME is hidden
+ mPipBoundsState.getNormalBounds().offset(0, mImeHeightAdjustment);
}
+ mImeVisible = imeVisible;
+ resizePinnedStack(STATE_PIP);
}
- });
+ }
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mHandler.post(() -> {
- mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
-
- mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
- mPipBounds.set(mPipBoundsAlgorithm.getNormalBounds());
- if (mDefaultPipBounds.isEmpty()) {
- mDefaultPipBounds.set(mPipBoundsAlgorithm.getDefaultBounds());
- }
- });
+ mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
+ mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
mCustomActions = actions;
- mHandler.post(() -> {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
- }
- });
+ mTvPipMenuController.setAppActions(mCustomActions);
}
}
@@ -228,6 +213,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
+ TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
PipNotification pipNotification,
TaskStackListenerImpl taskStackListener,
@@ -237,6 +223,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipNotification = pipNotification;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipMediaController = pipMediaController;
+ mTvPipMenuController = tvPipMenuController;
+ mTvPipMenuController.attachPipController(this);
// Ensure that we have the display info in case we get calls to update the bounds
// before the listener calls back
final DisplayInfo displayInfo = new DisplayInfo();
@@ -289,9 +277,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PipController.this.onActivityRestartAttempt(task, clearedTask);
}
});
-
- // TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
- PipMenuActivity.setPipController(this);
}
private void loadConfigurationsAndApply(Configuration newConfig) {
@@ -302,14 +287,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
return;
}
- Resources res = mContext.getResources();
- mMenuModePipBounds = Rect.unflattenFromString(res.getString(
- R.string.pip_menu_bounds));
+ final Rect menuBounds = Rect.unflattenFromString(
+ mContext.getResources().getString(R.string.pip_menu_bounds));
+ mPipBoundsState.setExpandedBounds(menuBounds);
- // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons.
- // 1. Configuration changed due to the language change (RTL <-> RTL)
- // 2. SystemUI restarts after the crash
- mPipBounds = mDefaultPipBounds;
resizePinnedStack(getPinnedTaskInfo() == null ? STATE_NO_PIP : STATE_PIP);
}
@@ -379,16 +360,22 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
private void onActivityPinned(String packageName) {
- if (DEBUG) Log.d(TAG, "onActivityPinned()");
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
+ final RootTaskInfo taskInfo = getPinnedTaskInfo();
+ if (DEBUG) Log.d(TAG, "onActivityPinned, task=" + taskInfo);
if (taskInfo == null) {
Log.w(TAG, "Cannot find pinned stack");
return;
}
- if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo);
+
+ // At this point PipBoundsState knows the correct aspect ratio for this pinned task, so we
+ // use PipBoundsAlgorithm to calculate the normal bounds for the task (PipBoundsAlgorithm
+ // will query PipBoundsState for the aspect ratio) and pass the bounds over to the
+ // PipBoundsState.
+ mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
+
mPinnedStackId = taskInfo.taskId;
mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
+
// Set state to STATE_PIP so we show it when the pinned stack animation ends.
mState = STATE_PIP;
mPipMediaController.onActivityPinned();
@@ -434,8 +421,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
if (getState() == STATE_PIP) {
- if (mPipBounds != mDefaultPipBounds) {
- mPipBounds = mDefaultPipBounds;
+ if (!Objects.equals(mPipBoundsState.getBounds(), mPipBoundsState.getNormalBounds())) {
resizePinnedStack(STATE_PIP);
}
}
@@ -451,7 +437,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
Log.d(TAG,
"suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
}
-
mSuspendPipResizingReason |= reason;
}
@@ -478,6 +463,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
* @param state In Pip state also used to determine the new size for the Pip.
*/
public void resizePinnedStack(int state) {
+
if (DEBUG) {
Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
+ getStateDescription(), new Exception());
@@ -509,11 +495,11 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
break;
case STATE_PIP_MENU:
- newBounds = mMenuModePipBounds;
+ newBounds = mPipBoundsState.getExpandedBounds();
break;
case STATE_PIP: // fallthrough
default:
- newBounds = mPipBounds;
+ newBounds = mPipBoundsState.getNormalBounds();
break;
}
if (newBounds != null) {
@@ -544,10 +530,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onShowPipMenu();
}
- Intent intent = new Intent(mContext, PipMenuActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
- mContext.startActivity(intent);
+
+ mTvPipMenuController.showMenu();
}
/**
@@ -650,8 +634,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
void onPipActivityClosed();
/** Invoked when the PIP menu gets shown. */
void onShowPipMenu();
- /** Invoked when the PIP menu actions change. */
- void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions);
/** Invoked when the PIPed activity is about to return back to the fullscreen. */
void onMoveToFullscreen();
/** Invoked when we are above to start resizing the Pip. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java
index d2270c278161..689c3ede9efa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java
@@ -18,109 +18,110 @@ package com.android.wm.shell.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
-import android.app.Activity;
+import android.annotation.Nullable;
import android.app.RemoteAction;
-import android.content.Intent;
+import android.content.Context;
import android.content.pm.ParceledListSlice;
-import android.os.Bundle;
import android.util.Log;
+import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
import com.android.wm.shell.R;
import java.util.Collections;
/**
- * Activity to show the PIP menu to control PIP.
- * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
+ * The Menu View that shows controls of the PiP. Always fullscreen.
*/
-public class PipMenuActivity extends Activity implements PipController.Listener {
- private static final String TAG = "PipMenuActivity";
+public class PipMenuView extends FrameLayout implements PipController.Listener {
+ private static final String TAG = "PipMenuView";
private static final boolean DEBUG = PipController.DEBUG;
- static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
-
- private static PipController sPipController;
-
- private Animator mFadeInAnimation;
- private Animator mFadeOutAnimation;
+ private final PipController mPipController;
+ private final Animator mFadeInAnimation;
+ private final Animator mFadeOutAnimation;
+ private final PipControlsViewController mPipControlsViewController;
private boolean mRestorePipSizeWhenClose;
- private PipControlsViewController mPipControlsViewController;
- @Override
- protected void onCreate(Bundle bundle) {
- if (DEBUG) Log.d(TAG, "onCreate()");
+ public PipMenuView(Context context, PipController pipController) {
+ super(context, null, 0);
+ mPipController = pipController;
+
+ inflate(context, R.layout.tv_pip_menu, this);
- super.onCreate(bundle);
- if (sPipController == null) {
- finish();
- }
- setContentView(R.layout.tv_pip_menu);
mPipControlsViewController = new PipControlsViewController(
- findViewById(R.id.pip_controls), sPipController);
- sPipController.addListener(this);
+ findViewById(R.id.pip_controls), mPipController);
mRestorePipSizeWhenClose = true;
mFadeInAnimation = AnimatorInflater.loadAnimator(
- this, R.anim.tv_pip_menu_fade_in_animation);
+ mContext, R.anim.tv_pip_menu_fade_in_animation);
mFadeInAnimation.setTarget(mPipControlsViewController.getView());
mFadeOutAnimation = AnimatorInflater.loadAnimator(
- this, R.anim.tv_pip_menu_fade_out_animation);
+ mContext, R.anim.tv_pip_menu_fade_out_animation);
mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
-
- onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
}
@Override
- protected void onNewIntent(Intent intent) {
- if (DEBUG) Log.d(TAG, "onNewIntent(), intent=" + intent);
- super.onNewIntent(intent);
-
- onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
+ && event.getAction() == KeyEvent.ACTION_UP) {
+ restorePipAndFinish();
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
}
- private void restorePipAndFinish() {
- if (DEBUG) Log.d(TAG, "restorePipAndFinish()");
-
- if (mRestorePipSizeWhenClose) {
- if (DEBUG) Log.d(TAG, " > restoring to the default position");
-
- // When PIP menu activity is closed, restore to the default position.
- sPipController.resizePinnedStack(PipController.STATE_PIP);
+ @Nullable
+ SurfaceControl getWindowSurfaceControl() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ }
+ final SurfaceControl out = root.getSurfaceControl();
+ if (out != null && out.isValid()) {
+ return out;
}
- finish();
+ return null;
}
- @Override
- public void onResume() {
- if (DEBUG) Log.d(TAG, "onResume()");
-
- super.onResume();
+ void showMenu() {
+ mPipController.addListener(this);
mFadeInAnimation.start();
+ setAlpha(1.0f);
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ getViewRootImpl().getInputToken(), true /* grantFocus */);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to update focus as menu appears", e);
+ }
}
- @Override
- public void onPause() {
- if (DEBUG) Log.d(TAG, "onPause()");
-
- super.onPause();
+ void hideMenu() {
+ mPipController.removeListener(this);
+ mPipController.resumePipResizing(
+ PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
mFadeOutAnimation.start();
- restorePipAndFinish();
+ setAlpha(0.0f);
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ getViewRootImpl().getInputToken(), false /* grantFocus */);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to update focus as menu disappears", e);
+ }
}
- @Override
- protected void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy()");
-
- super.onDestroy();
- sPipController.removeListener(this);
- sPipController.resumePipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
- }
+ private void restorePipAndFinish() {
+ if (DEBUG) Log.d(TAG, "restorePipAndFinish()");
- @Override
- public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed()");
+ if (mRestorePipSizeWhenClose) {
+ if (DEBUG) Log.d(TAG, " > restoring to the default position");
- restorePipAndFinish();
+ // When PIP menu activity is closed, restore to the default position.
+ mPipController.resizePinnedStack(PipController.STATE_PIP);
+ }
+ hideMenu();
}
@Override
@@ -132,11 +133,10 @@ public class PipMenuActivity extends Activity implements PipController.Listener
public void onPipActivityClosed() {
if (DEBUG) Log.d(TAG, "onPipActivityClosed()");
- finish();
+ hideMenu();
}
- @Override
- public void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ void setAppActions(ParceledListSlice<RemoteAction> actions) {
if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
@@ -156,34 +156,15 @@ public class PipMenuActivity extends Activity implements PipController.Listener
// Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds.
// This conflicts with restoring PIP position, so disable it.
mRestorePipSizeWhenClose = false;
- finish();
+ hideMenu();
}
@Override
public void onPipResizeAboutToStart() {
if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");
- finish();
- sPipController.suspendPipResizing(
+ hideMenu();
+ mPipController.suspendPipResizing(
PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
-
- @Override
- public void finish() {
- if (DEBUG) Log.d(TAG, "finish()", new RuntimeException());
-
- super.finish();
- }
-
- /**
- * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
- *
- * @param pipController The singleton pipController instance for TV
- */
- public static void setPipController(PipController pipController) {
- if (sPipController != null) {
- return;
- }
- sPipController = pipController;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
index b30dee4f331f..d56a88874420 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
@@ -19,12 +19,10 @@ package com.android.wm.shell.pip.tv;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.RemoteAction;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
@@ -99,11 +97,6 @@ public class PipNotification implements PipController.Listener {
}
@Override
- public void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions) {
- // no-op.
- }
-
- @Override
public void onMoveToFullscreen() {
dismissPipNotification();
mPackageName = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
new file mode 100644
index 000000000000..91aef670b946
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.pip.tv;
+
+import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+
+/**
+ * Manages the visibility of the PiP Menu as user interacts with PiP.
+ */
+public class TvPipMenuController implements PipMenuController {
+
+ private final Context mContext;
+ private final SystemWindows mSystemWindows;
+ private final PipBoundsState mPipBoundsState;
+ private PipMenuView mMenuView;
+ private PipController mPipController;
+ private SurfaceControl mLeash;
+
+ public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
+ SystemWindows systemWindows) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mSystemWindows = systemWindows;
+ }
+
+ void attachPipController(PipController pipController) {
+ mPipController = pipController;
+ }
+
+ @Override
+ public void showMenu() {
+ if (mMenuView != null) {
+ mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
+ mPipBoundsState.getDisplayBounds().width(),
+ mPipBoundsState.getDisplayBounds().height()));
+ mMenuView.showMenu();
+
+ // By default, SystemWindows views are above everything else.
+ // Set the relative z-order so the menu is below PiP.
+ if (mMenuView.getWindowSurfaceControl() != null && mLeash != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, -1);
+ t.apply();
+ }
+ }
+ }
+
+ @Override
+ public void attach(SurfaceControl leash) {
+ if (mMenuView == null) {
+ mMenuView = new PipMenuView(mContext, mPipController);
+ mSystemWindows.addView(mMenuView,
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ 0, SHELL_ROOT_LAYER_PIP);
+ mLeash = leash;
+ }
+ }
+
+ @Override
+ public void detach() {
+ mSystemWindows.removeView(mMenuView);
+ mMenuView = null;
+ mLeash = null;
+ }
+
+ @Override
+ public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
+ mMenuView.setAppActions(appActions);
+ }
+
+ @Override
+ public boolean isMenuVisible() {
+ return mMenuView != null && mMenuView.getAlpha() == 1.0f;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java
index f2becc99eae8..5078371dab64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java
@@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
@@ -59,7 +60,7 @@ final class DividerWindowManager {
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.setTitle(WINDOW_TITLE);
- mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e55f065c1bb2..7c70a4efad91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.splitscreen;
import android.graphics.Rect;
import android.window.WindowContainerToken;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.PrintWriter;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -26,6 +28,7 @@ import java.util.function.Consumer;
/**
* Interface to engage split screen feature.
*/
+@ExternalThread
public interface SplitScreen {
/** Called when keyguard showing state changed. */
void onKeyguardVisibilityChanged(boolean isShowing);
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index 101b5bf27c77..1054c4345891 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -21,6 +21,8 @@
<!-- Read and write traces from external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Write secure settings -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<!-- Capture screen contents -->
@@ -38,7 +40,8 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
<!-- ATM.removeRootTasksWithActivityTypes() -->
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
- <application>
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <application android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
index 9dd9f42bdf81..23d7021baffb 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
@@ -34,7 +34,7 @@
<option name="hidden-api-checks" value="false" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/storage/emulated/0/Android/data/com.android.wm.shell.flicker/files" />
+ <option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
index afb1166415fc..073860875004 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
@@ -34,7 +34,7 @@
<option name="hidden-api-checks" value="false" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/storage/emulated/0/Android/data/com.android.wm.shell.flicker/files" />
+ <option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 0fb43e263d05..3ed53fb221a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,78 +18,6 @@ package com.android.wm.shell.flicker
import com.android.server.wm.flicker.dsl.EventLogAssertion
import com.android.server.wm.flicker.dsl.LayersAssertion
-import com.android.server.wm.flicker.dsl.WmAssertion
-import com.android.server.wm.flicker.helpers.WindowUtils
-
-@JvmOverloads
-fun WmAssertion.statusBarWindowIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("statusBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
- }
-}
-
-@JvmOverloads
-fun WmAssertion.navBarWindowIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("navBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.noUncoveredRegions(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- allStates: Boolean = true,
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
- val endingBounds = WindowUtils.getDisplayBounds(endRotation)
- if (allStates) {
- all("noUncoveredRegions", bugId, enabled) {
- if (startingBounds == endingBounds) {
- this.coversAtLeastRegion(startingBounds)
- } else {
- this.coversAtLeastRegion(startingBounds)
- .then()
- .coversAtLeastRegion(endingBounds)
- }
- }
- } else {
- start("noUncoveredRegions_StartingPos") {
- this.coversAtLeastRegion(startingBounds)
- }
- end("noUncoveredRegions_EndingPos") {
- this.coversAtLeastRegion(endingBounds)
- }
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.statusBarLayerIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.navBarLayerIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("navBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
- }
-}
@JvmOverloads
fun LayersAssertion.appPairsDividerIsVisible(
@@ -131,48 +59,6 @@ fun LayersAssertion.dockedStackDividerIsInvisible(
}
}
-@JvmOverloads
-fun LayersAssertion.navBarLayerRotatesAndScales(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- val startingPos = WindowUtils.getNavigationBarPosition(beginRotation)
- val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
-
- start("navBarLayerRotatesAndScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- }
- end("navBarLayerRotatesAndScales_EndingPost", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, endingPos)
- }
-
- if (startingPos == endingPos) {
- all("navBarLayerRotatesAndScales", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- }
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.statusBarLayerRotatesScales(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- val startingPos = WindowUtils.getStatusBarPosition(beginRotation)
- val endingPos = WindowUtils.getStatusBarPosition(endRotation)
-
- start("statusBarLayerRotatesScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, startingPos)
- }
- end("statusBarLayerRotatesScales_EndingPos", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, endingPos)
- }
-}
-
fun EventLogAssertion.focusChanges(
vararg windows: String,
bugId: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
index ced99de21a46..7ac91b065fca 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
@@ -29,10 +29,10 @@ import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.TEST_REPETITIONS
import com.android.wm.shell.flicker.appPairsDividerIsInvisible
import com.android.wm.shell.flicker.appPairsDividerIsVisible
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index 0663eb344f46..c9396aa7ea63 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -23,10 +23,10 @@ import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
index 322034ce7688..76aabc1b83cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
@@ -24,10 +24,10 @@ import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 96d98d56e069..a67b3b760c49 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -31,13 +31,13 @@ import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarLayerRotatesAndScales
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.noUncoveredRegions
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerRotatesScales
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt
index d20552f0739d..f79b21ff278d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt
@@ -28,10 +28,10 @@ import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
index c61a0f171714..5570a562a515 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
@@ -25,10 +25,10 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.dockedStackDividerIsVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
index bf9286980b9a..7c47d1f1b1ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
@@ -29,8 +29,8 @@ import com.android.server.wm.flicker.helpers.resizeSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt
index 1e328a8dae40..c85561d96091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.FlakyTest
+import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
@@ -54,7 +54,6 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 161435597)
class OpenAppToSplitScreenTest(
testName: String,
flickerSpec: Flicker
@@ -67,7 +66,8 @@ class OpenAppToSplitScreenTest(
val testApp = StandardAppHelper(instrumentation,
"com.android.wm.shell.flicker.testapp", "SimpleApp")
- return FlickerTestRunnerFactory(instrumentation)
+ // b/161435597 causes the test not to work on 90 degrees
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
.buildTest { configuration ->
withTestName {
buildTestTag("appToSplitScreen", testApp, configuration)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt
new file mode 100644
index 000000000000..d2371bd766f5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.flicker.splitscreen
+
+import androidx.test.filters.FlakyTest
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.repetitions
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:SplitScreenRotateOneLaunchedAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest
+class SplitScreenRotateOneLaunchedAppTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+
+ return FlickerTestRunnerFactory(instrumentation, repetitions = 3)
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("splitScreenRotateOneApp", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.open()
+ device.launchSplitScreen()
+ device.waitForIdle()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ testApp.exit()
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
+ this.setRotation(configuration.endRotation)
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt
new file mode 100644
index 000000000000..67346424acd2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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.flicker.splitscreen
+
+import androidx.test.filters.FlakyTest
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.repetitions
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:SplitScreenRotateTwoLaunchedAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest
+class SplitScreenRotateTwoLaunchedAppTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+ val secondaryApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp",
+ "SplitScreenSecondaryApp")
+
+ return FlickerTestRunnerFactory(instrumentation, repetitions = 3)
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("splitScreenRotateTwoApps", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.open()
+ device.pressHome()
+ secondaryApp.open()
+ device.pressHome()
+ device.launchSplitScreen()
+ device.reopenAppFromOverview()
+ device.waitForIdle()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ testApp.exit()
+ secondaryApp.exit()
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
+ this.setRotation(configuration.endRotation)
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
index 754f73246c86..8dbc1d56bcc2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -52,19 +51,17 @@ public class AppPairTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
- @Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController,
- mTaskStackListener);
when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
when(mDisplayController.getDisplay(anyInt())).thenReturn(
mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+ mController = new TestAppPairsController(
+ mTaskOrganizer,
+ mSyncQueue,
+ mDisplayController);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
index 6d441ab898ec..fada694a4c07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -52,20 +51,18 @@ public class AppPairsControllerTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
- @Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
+ when(mDisplayController.getDisplay(anyInt())).thenReturn(
+ mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
- mDisplayController,
- mTaskStackListener);
+ mDisplayController);
mPool = mController.getPool();
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- when(mDisplayController.getDisplay(anyInt())).thenReturn(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
index d3dbbfe37985..a3f134ee97ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
@@ -18,13 +18,16 @@ package com.android.wm.shell.apppairs;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -36,22 +39,21 @@ import org.mockito.MockitoAnnotations;
/** Tests for {@link AppPairsPool} */
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class AppPairsPoolTests {
+public class AppPairsPoolTests extends ShellTestCase {
private TestAppPairsController mController;
private TestAppPairsPool mPool;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
- @Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
- mDisplayController,
- mTaskStackListener);
+ mDisplayController);
mPool = mController.getPool();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
index e61cc91c394b..be0963628933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
@@ -19,14 +19,13 @@ package com.android.wm.shell.apppairs;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
public class TestAppPairsController extends AppPairsController {
TestAppPairsPool mPool;
public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController, TaskStackListenerImpl taskStackListener) {
- super(organizer, syncQueue, displayController, taskStackListener);
+ DisplayController displayController) {
+ super(organizer, syncQueue, displayController);
mPool = new TestAppPairsPool(this);
setPairsPool(mPool);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 080cddc58a09..5e0d51809d44 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -20,12 +20,15 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.Surface.ROTATION_0;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.graphics.Point;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -68,19 +71,31 @@ public class DisplayImeControllerTest {
@Test
public void reappliesVisibilityToChangedLeash() {
verifyZeroInteractions(mT);
+ mPerDisplay.mImeShowing = true;
- mPerDisplay.mImeShowing = false;
- mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] {
- new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
- });
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
+ assertFalse(mPerDisplay.mImeShowing);
verify(mT).hide(any());
mPerDisplay.mImeShowing = true;
- mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] {
- new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
- });
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(true), insetsSourceControl());
+ assertTrue(mPerDisplay.mImeShowing);
verify(mT).show(any());
}
+
+ private InsetsSourceControl[] insetsSourceControl() {
+ return new InsetsSourceControl[]{
+ new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
+ };
+ }
+
+ private InsetsState insetsStateWithIme(boolean visible) {
+ InsetsState state = new InsetsState();
+ state.addSource(new InsetsSource(ITYPE_IME));
+ state.setSourceVisible(ITYPE_IME, visible);
+ return state;
+ }
+
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
new file mode 100644
index 000000000000..d87f4c60fad4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 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.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link SplitLayout} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitLayoutTests extends ShellTestCase {
+ @Mock SplitLayout.LayoutChangeListener mLayoutChangeListener;
+ @Mock SurfaceControl mRootLeash;
+ private SplitLayout mSplitLayout;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mSplitLayout = new SplitLayout(
+ mContext,
+ getConfiguration(false),
+ mLayoutChangeListener,
+ mRootLeash);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUpdateConfiguration() {
+ assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse();
+ assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue();
+ }
+
+ @Test
+ public void testUpdateDividePosition() {
+ mSplitLayout.updateDividePosition(anyInt());
+ verify(mLayoutChangeListener).onBoundsChanging(any(SplitLayout.class));
+ }
+
+ @Test
+ public void testSetSnapTarget() {
+ DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0,
+ DividerSnapAlgorithm.SnapTarget.FLAG_NONE);
+ mSplitLayout.setSnapTarget(snapTarget);
+ verify(mLayoutChangeListener).onBoundsChanged(any(SplitLayout.class));
+
+ // verify it callbacks properly when the snap target indicates dismissing split.
+ snapTarget = getSnapTarget(0, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+ mSplitLayout.setSnapTarget(snapTarget);
+ verify(mLayoutChangeListener).onSnappedToDismiss(eq(false));
+ snapTarget = getSnapTarget(0, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+ mSplitLayout.setSnapTarget(snapTarget);
+ verify(mLayoutChangeListener).onSnappedToDismiss(eq(true));
+ }
+
+ private static Configuration getConfiguration(boolean isLandscape) {
+ final Configuration configuration = new Configuration();
+ configuration.unset();
+ configuration.orientation = isLandscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ configuration.windowConfiguration.setBounds(
+ new Rect(0, 0, isLandscape ? 2160 : 1080, isLandscape ? 1080 : 2160));
+ return configuration;
+ }
+
+ private static DividerSnapAlgorithm.SnapTarget getSnapTarget(int position, int flag) {
+ return new DividerSnapAlgorithm.SnapTarget(
+ position /* position */, position /* taskPosition */, flag);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
new file mode 100644
index 000000000000..aa0eb2f95ed8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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.split;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link SplitWindowManager} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitWindowManagerTests extends ShellTestCase {
+ @Mock SurfaceControl mSurfaceControl;
+ @Mock SplitLayout mSplitLayout;
+ private SplitWindowManager mSplitWindowManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ final Configuration configuration = new Configuration();
+ configuration.setToDefaults();
+ mSplitWindowManager = new SplitWindowManager(mContext, configuration, mSurfaceControl);
+ when(mSplitLayout.getDividerBounds()).thenReturn(
+ new Rect(0, 0, configuration.windowConfiguration.getBounds().width(),
+ configuration.windowConfiguration.getBounds().height()));
+ }
+
+ @Test
+ @UiThreadTest
+ public void testInitRelease() {
+ mSplitWindowManager.init(mSplitLayout);
+ assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
+ mSplitWindowManager.release();
+ assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 55e7a354f4cd..7f280cd124d2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -79,7 +79,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), new Rect(), null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, new Rect(), new Rect(), new Rect(), null,
+ TRANSITION_DIRECTION_TO_PIP);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -87,16 +88,19 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void getAnimator_whenSameTypeRunning_updateExistingAnimator() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+ TRANSITION_DIRECTION_TO_PIP);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue2, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue2, null,
+ TRANSITION_DIRECTION_TO_PIP);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -122,11 +126,13 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
@SuppressWarnings("unchecked")
public void pipTransitionAnimator_updateEndValue() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+ TRANSITION_DIRECTION_TO_PIP);
animator.updateEndValue(endValue2);
@@ -135,10 +141,12 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void pipTransitionAnimator_setPipAnimationCallback() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 8d3774cee1e0..45e4241d5bc6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,7 +44,7 @@ import android.window.WindowContainerToken;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.pip.phone.PipMenuActivityController;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
@@ -66,7 +66,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private DisplayController mMockdDisplayController;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
- @Mock private PipMenuActivityController mMenuActivityController;
+ @Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<SplitScreen> mMockOptionalSplitScreen;
@@ -83,9 +83,9 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext);
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
- mMockPipBoundsAlgorithm, mMenuActivityController, mMockPipSurfaceTransactionHelper,
- mMockOptionalSplitScreen, mMockdDisplayController, mMockPipUiEventLogger,
- mMockShellTaskOrganizer));
+ mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
+ mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
+ mMockPipUiEventLogger, mMockShellTaskOrganizer));
preparePipTaskOrg();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 88c8eb902a6f..4687d2d9667c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -61,7 +61,7 @@ public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
@Mock private DisplayController mMockDisplayController;
- @Mock private PipMenuActivityController mMockPipMenuActivityController;
+ @Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@@ -77,7 +77,7 @@ public class PipControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mPipController = new PipController(mContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
- mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
mMockExecutor);
doAnswer(invocation -> {
@@ -110,7 +110,7 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
- mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
mMockExecutor));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index abbc681f53fe..4efaebf96c2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -60,7 +61,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipTouchHandler mPipTouchHandler;
@Mock
- private PipMenuActivityController mPipMenuActivityController;
+ private PhonePipMenuController mPhonePipMenuController;
@Mock
private PipTaskOrganizer mPipTaskOrganizer;
@@ -71,6 +72,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Mock
private PipUiEventLogger mPipUiEventLogger;
+ @Mock
+ private ShellExecutor mShellMainExecutor;
+
private PipBoundsState mPipBoundsState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSnapAlgorithm mPipSnapAlgorithm;
@@ -92,9 +96,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
mPipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
mPipSnapAlgorithm = new PipSnapAlgorithm();
- mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
+ mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
- mFloatingContentCoordinator, mPipUiEventLogger);
+ mFloatingContentCoordinator, mPipUiEventLogger, mShellMainExecutor);
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index cd53217d2924..1ff1978044b9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -333,6 +333,7 @@ cc_defaults {
"jni/YuvToJpegEncoder.cpp",
"jni/fonts/Font.cpp",
"jni/fonts/FontFamily.cpp",
+ "jni/fonts/NativeFont.cpp",
"jni/text/LineBreaker.cpp",
"jni/text/MeasuredText.cpp",
"jni/text/TextShaper.cpp",
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index fd18d2f9192d..8b20492543f7 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -20,7 +20,7 @@
namespace android {
namespace uirenderer {
-const std::string FrameInfoNames[] = {
+const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames = {
"Flags",
"FrameTimelineVsyncId",
"IntendedVsync",
@@ -42,10 +42,6 @@ const std::string FrameInfoNames[] = {
"GpuCompleted",
};
-static_assert((sizeof(FrameInfoNames) / sizeof(FrameInfoNames[0])) ==
- static_cast<int>(FrameInfoIndex::NumIndexes),
- "size mismatch: FrameInfoNames doesn't match the enum!");
-
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index bb875e35f6f7..738246d56d0d 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -21,6 +21,7 @@
#include <cutils/compiler.h>
#include <utils/Timers.h>
+#include <array>
#include <memory.h>
#include <string>
@@ -60,7 +61,7 @@ enum class FrameInfoIndex {
NumIndexes
};
-extern const std::string FrameInfoNames[];
+extern const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames;
namespace FrameInfoFlags {
enum {
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index c174c240ff22..f4c633fbe58f 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -18,6 +18,7 @@
#include "renderstate/RenderState.h"
#include "utils/Color.h"
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
@@ -52,5 +53,90 @@ SkBlendMode Layer::getMode() const {
}
}
+static inline SkScalar isIntegerAligned(SkScalar x) {
+ return fabsf(roundf(x) - x) <= NON_ZERO_EPSILON;
+}
+
+// Disable filtering when there is no scaling in screen coordinates and the corners have the same
+// fraction (for translate) or zero fraction (for any other rect-to-rect transform).
+static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, const SkRect& dstRect) {
+ if (!matrix.rectStaysRect()) return true;
+ SkRect dstDevRect = matrix.mapRect(dstRect);
+ float dstW, dstH;
+ if (MathUtils::isZero(matrix.getScaleX()) && MathUtils::isZero(matrix.getScaleY())) {
+ // Has a 90 or 270 degree rotation, although total matrix may also have scale factors
+ // in m10 and m01. Those scalings are automatically handled by mapRect so comparing
+ // dimensions is sufficient, but swap width and height comparison.
+ dstW = dstDevRect.height();
+ dstH = dstDevRect.width();
+ } else {
+ // Handle H/V flips or 180 rotation matrices. Axes may have been mirrored, but
+ // dimensions are still safe to compare directly.
+ dstW = dstDevRect.width();
+ dstH = dstDevRect.height();
+ }
+ if (!(MathUtils::areEqual(dstW, srcRect.width()) &&
+ MathUtils::areEqual(dstH, srcRect.height()))) {
+ return true;
+ }
+ // Device rect and source rect should be integer aligned to ensure there's no difference
+ // in how nearest-neighbor sampling is resolved.
+ return !(isIntegerAligned(srcRect.x()) &&
+ isIntegerAligned(srcRect.y()) &&
+ isIntegerAligned(dstDevRect.x()) &&
+ isIntegerAligned(dstDevRect.y()));
+}
+
+void Layer::draw(SkCanvas* canvas) {
+ GrRecordingContext* context = canvas->recordingContext();
+ if (context == nullptr) {
+ SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+ return;
+ }
+ SkMatrix layerTransform = getTransform();
+ //sk_sp<SkImage> layerImage = getImage();
+ const int layerWidth = getWidth();
+ const int layerHeight = getHeight();
+ if (layerImage) {
+ SkMatrix textureMatrixInv;
+ textureMatrixInv = getTexTransform();
+ // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+ // use bottom left origin and remove flipV and invert transformations.
+ SkMatrix flipV;
+ flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+ textureMatrixInv.preConcat(flipV);
+ textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight);
+ textureMatrixInv.postScale(layerImage->width(), layerImage->height());
+ SkMatrix textureMatrix;
+ if (!textureMatrixInv.invert(&textureMatrix)) {
+ textureMatrix = textureMatrixInv;
+ }
+
+ SkMatrix matrix;
+ matrix = SkMatrix::Concat(layerTransform, textureMatrix);
+
+ SkPaint paint;
+ paint.setAlpha(getAlpha());
+ paint.setBlendMode(getMode());
+ paint.setColorFilter(getColorFilter());
+ const bool nonIdentityMatrix = !matrix.isIdentity();
+ if (nonIdentityMatrix) {
+ canvas->save();
+ canvas->concat(matrix);
+ }
+ const SkMatrix& totalMatrix = canvas->getTotalMatrix();
+
+ SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
+ if (getForceFilter() || shouldFilterRect(totalMatrix, imageRect, imageRect)) {
+ paint.setFilterQuality(kLow_SkFilterQuality);
+ }
+ canvas->drawImage(layerImage.get(), 0, 0, &paint);
+ // restore the original matrix
+ if (nonIdentityMatrix) {
+ canvas->restore();
+ }
+ }
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index ea3bfc9e80cb..e99e76299317 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -21,6 +21,7 @@
#include <SkBlendMode.h>
#include <SkColorFilter.h>
#include <SkColorSpace.h>
+#include <SkCanvas.h>
#include <SkPaint.h>
#include <SkImage.h>
#include <SkMatrix.h>
@@ -87,6 +88,8 @@ public:
inline sk_sp<SkImage> getImage() const { return this->layerImage; }
+ void draw(SkCanvas* canvas);
+
protected:
RenderState& mRenderState;
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e1f5abd786bf..0fad2d58cc8a 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -69,6 +69,7 @@ extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_fonts_Font(JNIEnv* env);
extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
+extern int register_android_graphics_fonts_NativeFont(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
@@ -135,6 +136,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_drawable_VectorDrawable),
REG_JNI(register_android_graphics_fonts_Font),
REG_JNI(register_android_graphics_fonts_FontFamily),
+ REG_JNI(register_android_graphics_fonts_NativeFont),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
REG_JNI(register_android_graphics_pdf_PdfRenderer),
diff --git a/libs/hwui/canvas/CanvasOpRasterizer.cpp b/libs/hwui/canvas/CanvasOpRasterizer.cpp
index 25129f641c00..0093c38cf8a8 100644
--- a/libs/hwui/canvas/CanvasOpRasterizer.cpp
+++ b/libs/hwui/canvas/CanvasOpRasterizer.cpp
@@ -33,7 +33,11 @@ void rasterizeCanvasBuffer(const CanvasOpBuffer& source, SkCanvas* destination)
SkMatrix& currentGlobalTransform = globalMatrixStack.emplace_back(SkMatrix::I());
source.for_each([&]<CanvasOpType T>(const CanvasOpContainer<T> * op) {
- if constexpr (T == CanvasOpType::BeginZ || T == CanvasOpType::EndZ) {
+ if constexpr (
+ T == CanvasOpType::BeginZ ||
+ T == CanvasOpType::EndZ ||
+ T == CanvasOpType::DrawLayer
+ ) {
// Do beginZ or endZ
LOG_ALWAYS_FATAL("TODO");
return;
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index f9df2f7aa5ba..f0aa7774a6cc 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -47,14 +47,17 @@ enum class CanvasOpType : int8_t {
DrawArc,
DrawPaint,
DrawPoint,
+ DrawPoints,
DrawPath,
DrawLine,
+ DrawLines,
DrawVertices,
DrawImage,
DrawImageRect,
// DrawImageLattice also used to draw 9 patches
DrawImageLattice,
DrawPicture,
+ DrawLayer,
// TODO: Rest
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 8c7113d5d075..242dbdb27362 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -26,8 +26,10 @@
#include <hwui/Bitmap.h>
#include <log/log.h>
#include "CanvasProperty.h"
+#include "Points.h"
#include "CanvasOpTypes.h"
+#include "Layer.h"
#include <experimental/type_traits>
#include <utility>
@@ -165,6 +167,22 @@ struct CanvasOp<CanvasOpType::DrawPoint> {
};
template <>
+struct CanvasOp<CanvasOpType::DrawPoints> {
+ size_t count;
+ SkPaint paint;
+ sk_sp<Points> points;
+ void draw(SkCanvas* canvas) const {
+ canvas->drawPoints(
+ SkCanvas::kPoints_PointMode,
+ count,
+ points->data(),
+ paint
+ );
+ }
+ ASSERT_DRAWABLE()
+};
+
+template <>
struct CanvasOp<CanvasOpType::DrawRect> {
SkRect rect;
SkPaint paint;
@@ -263,6 +281,22 @@ struct CanvasOp<CanvasOpType::DrawLine> {
};
template<>
+struct CanvasOp<CanvasOpType::DrawLines> {
+ size_t count;
+ SkPaint paint;
+ sk_sp<Points> points;
+ void draw(SkCanvas* canvas) const {
+ canvas->drawPoints(
+ SkCanvas::kLines_PointMode,
+ count,
+ points->data(),
+ paint
+ );
+ }
+ ASSERT_DRAWABLE()
+};
+
+template<>
struct CanvasOp<CanvasOpType::DrawVertices> {
sk_sp<SkVertices> vertices;
SkBlendMode mode;
@@ -364,6 +398,11 @@ struct CanvasOp<CanvasOpType::DrawPicture> {
}
};
+template<>
+struct CanvasOp<CanvasOpType::DrawLayer> {
+ sp<Layer> layer;
+};
+
// cleanup our macros
#undef ASSERT_DRAWABLE
diff --git a/libs/hwui/canvas/Points.h b/libs/hwui/canvas/Points.h
new file mode 100644
index 000000000000..05e6a7dd5884
--- /dev/null
+++ b/libs/hwui/canvas/Points.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+#include <ui/FatVector.h>
+#include "SkPoint.h"
+#include "SkRefCnt.h"
+
+/**
+ * Collection of points that are ref counted and to be used with
+ * various drawing calls that consume SkPoint as inputs like
+ * drawLines/drawPoints
+ */
+class Points: public SkNVRefCnt<SkPoint> {
+public:
+ Points(int size){
+ skPoints.resize(size);
+ }
+
+ Points(std::initializer_list<SkPoint> init): skPoints(init) { }
+
+ SkPoint& operator[](int index) {
+ return skPoints[index];
+ }
+
+ const SkPoint* data() const {
+ return skPoints.data();
+ }
+
+ size_t size() const {
+ return skPoints.size();
+ }
+private:
+ // Initialize the size to contain 2 SkPoints on the stack for optimized
+ // drawLine calls that require 2 SkPoints for start/end points of the line
+ android::FatVector<SkPoint, 2> skPoints;
+};
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f0c77930cbe3..f612bce748ff 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -187,38 +187,6 @@ static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong
}
// Critical Native
-static jlong Font_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
- const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
-
- uint64_t result = font->style().weight();
- result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
- result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
- result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
- return result;
-}
-
-// Critical Native
-static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
- const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
- const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
- uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
- return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
-}
-
-// FastNative
-static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
- const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
- const std::string& filePath = minikinSkia->getFilePath();
- if (filePath.empty()) {
- return nullptr;
- }
- return env->NewStringUTF(filePath.c_str());
-}
-
-// Critical Native
static jlong Font_getNativeFontPtr(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
return reinterpret_cast<jlong>(font->font.get());
@@ -276,9 +244,6 @@ static const JNINativeMethod gFontBuilderMethods[] = {
static const JNINativeMethod gFontMethods[] = {
{ "nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*) Font_getGlyphBounds },
{ "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics },
- { "nGetFontInfo", "(J)J", (void*) Font_getFontInfo },
- { "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo },
- { "nGetFontPath", "(J)Ljava/lang/String;", (void*) Font_getFontPath },
{ "nGetNativeFontPtr", "(J)J", (void*) Font_getNativeFontPtr },
{ "nGetFontBufferAddress", "(J)J", (void*) Font_GetBufferAddress },
};
diff --git a/libs/hwui/jni/fonts/NativeFont.cpp b/libs/hwui/jni/fonts/NativeFont.cpp
new file mode 100644
index 000000000000..c5c5d464ccac
--- /dev/null
+++ b/libs/hwui/jni/fonts/NativeFont.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Minikin"
+
+#include "Font.h"
+#include "SkData.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+#include "SkTypeface.h"
+#include "GraphicsJNI.h"
+#include <nativehelper/ScopedUtfChars.h>
+#include "Utils.h"
+#include "FontUtils.h"
+
+#include <hwui/MinikinSkia.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/FontFamily.h>
+#include <minikin/LocaleList.h>
+#include <ui/FatVector.h>
+
+#include <memory>
+
+namespace android {
+
+// Critical Native
+static jint NativeFont_getFamilyCount(CRITICAL_JNI_PARAMS_COMMA jlong typefaceHandle) {
+ Typeface* tf = reinterpret_cast<Typeface*>(typefaceHandle);
+ return tf->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong NativeFont_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong typefaceHandle, jint index) {
+ Typeface* tf = reinterpret_cast<Typeface*>(typefaceHandle);
+ return reinterpret_cast<jlong>(tf->fFontCollection->getFamilies()[index].get());
+
+}
+
+// Fast Native
+static jstring NativeFont_getLocaleList(JNIEnv* env, jobject, jlong familyHandle) {
+ minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+ uint32_t localeListId = family->localeListId();
+ return env->NewStringUTF(minikin::getLocaleString(localeListId).c_str());
+}
+
+// Critical Native
+static jint NativeFont_getFontCount(CRITICAL_JNI_PARAMS_COMMA jlong familyHandle) {
+ minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+ return family->getNumFonts();
+}
+
+// Critical Native
+static jlong NativeFont_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyHandle, jint index) {
+ minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+ return reinterpret_cast<jlong>(family->getFont(index));
+}
+
+// Critical Native
+static jlong NativeFont_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+
+ uint64_t result = font->style().weight();
+ result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
+ result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
+ result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
+ return result;
+}
+
+// Critical Native
+static jlong NativeFont_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+ const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
+ uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
+ return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
+}
+
+// FastNative
+static jstring NativeFont_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+ const std::string& filePath = minikinSkia->getFilePath();
+ if (filePath.empty()) {
+ return nullptr;
+ }
+ return env->NewStringUTF(filePath.c_str());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const JNINativeMethod gNativeFontMethods[] = {
+ { "nGetFamilyCount", "(J)I", (void*) NativeFont_getFamilyCount },
+ { "nGetFamily", "(JI)J", (void*) NativeFont_getFamily },
+ { "nGetLocaleList", "(J)Ljava/lang/String;", (void*) NativeFont_getLocaleList },
+ { "nGetFontCount", "(J)I", (void*) NativeFont_getFontCount },
+ { "nGetFont", "(JI)J", (void*) NativeFont_getFont },
+ { "nGetFontInfo", "(J)J", (void*) NativeFont_getFontInfo },
+ { "nGetAxisInfo", "(JI)J", (void*) NativeFont_getAxisInfo },
+ { "nGetFontPath", "(J)Ljava/lang/String;", (void*) NativeFont_getFontPath },
+};
+
+int register_android_graphics_fonts_NativeFont(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/fonts/NativeFont", gNativeFontMethods,
+ NELEM(gNativeFontMethods));
+}
+
+} // namespace android
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index f186e55ec2e3..033a5872ead3 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -245,6 +245,31 @@ TEST(CanvasOp, simpleDrawPoint) {
EXPECT_EQ(1, canvas.sumTotalDrawCalls());
}
+TEST(CanvasOp, simpleDrawPoints) {
+ CanvasOpBuffer buffer;
+ EXPECT_EQ(buffer.size(), 0);
+ size_t numPts = 3;
+ auto pts = sk_ref_sp(
+ new Points({
+ {32, 16},
+ {48, 48},
+ {16, 32}
+ })
+ );
+
+ buffer.push(CanvasOp<Op::DrawPoints> {
+ .count = numPts,
+ .paint = SkPaint{},
+ .points = pts
+ });
+
+ CallCountingCanvas canvas;
+ EXPECT_EQ(0, canvas.sumTotalDrawCalls());
+ rasterizeCanvasBuffer(buffer, &canvas);
+ EXPECT_EQ(1, canvas.drawPoints);
+ EXPECT_EQ(1, canvas.sumTotalDrawCalls());
+}
+
TEST(CanvasOp, simpleDrawLine) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
@@ -263,6 +288,30 @@ TEST(CanvasOp, simpleDrawLine) {
EXPECT_EQ(1, canvas.sumTotalDrawCalls());
}
+TEST(CanvasOp, simpleDrawLines) {
+ CanvasOpBuffer buffer;
+ EXPECT_EQ(buffer.size(), 0);
+ size_t numPts = 3;
+ auto pts = sk_ref_sp(
+ new Points({
+ {32, 16},
+ {48, 48},
+ {16, 32}
+ })
+ );
+ buffer.push(CanvasOp<Op::DrawLines> {
+ .count = numPts,
+ .paint = SkPaint{},
+ .points = pts
+ });
+
+ CallCountingCanvas canvas;
+ EXPECT_EQ(0, canvas.sumTotalDrawCalls());
+ rasterizeCanvasBuffer(buffer, &canvas);
+ EXPECT_EQ(1, canvas.drawPoints);
+ EXPECT_EQ(1, canvas.sumTotalDrawCalls());
+}
+
TEST(CanvasOp, simpleDrawRect) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
diff --git a/libs/incident/OWNERS b/libs/incident/OWNERS
new file mode 100644
index 000000000000..f76611555dbb
--- /dev/null
+++ b/libs/incident/OWNERS
@@ -0,0 +1 @@
+include /cmds/incidentd/OWNERS
diff --git a/libs/input/OWNERS b/libs/input/OWNERS
new file mode 100644
index 000000000000..d701f23cb9b8
--- /dev/null
+++ b/libs/input/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/hardware/input/OWNERS
diff --git a/libs/storage/OWNERS b/libs/storage/OWNERS
new file mode 100644
index 000000000000..6f9dbea36b06
--- /dev/null
+++ b/libs/storage/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/os/storage/OWNERS
diff --git a/libs/usb/OWNERS b/libs/usb/OWNERS
new file mode 100644
index 000000000000..f7b2a37a297a
--- /dev/null
+++ b/libs/usb/OWNERS
@@ -0,0 +1 @@
+include /services/usb/OWNERS