diff options
Diffstat (limited to 'libs')
129 files changed, 3092 insertions, 1082 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 0a5a81b4fd8f..a0d6fce4c39b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -33,6 +33,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; +import android.annotation.DimenRes; import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; @@ -61,7 +62,6 @@ import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; -import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.window.extensions.core.util.function.Consumer; import androidx.window.extensions.embedding.SplitAttributes.SplitType; @@ -341,7 +341,7 @@ class DividerPresenter implements View.OnTouchListener { applicationContext.getResources().getDisplayMetrics()); } - private static int getDimensionDp(@IdRes int resId) { + private static int getDimensionDp(@DimenRes int resId) { final Context context = ActivityThread.currentActivityThread().getApplication(); final int px = context.getResources().getDimensionPixelSize(resId); return (int) TypedValue.convertPixelsToDimension( @@ -431,6 +431,9 @@ class DividerPresenter implements View.OnTouchListener { return null; } int widthDp = dividerAttributes.getWidthDp(); + float minRatio = dividerAttributes.getPrimaryMinRatio(); + float maxRatio = dividerAttributes.getPrimaryMaxRatio(); + if (widthDp == WIDTH_SYSTEM_DEFAULT) { widthDp = DEFAULT_DIVIDER_WIDTH_DP; } @@ -439,16 +442,14 @@ class DividerPresenter implements View.OnTouchListener { // Draggable divider width must be larger than the drag handle size. widthDp = Math.max(widthDp, getDimensionDp(R.dimen.activity_embedding_divider_touch_target_width)); - } - - float minRatio = dividerAttributes.getPrimaryMinRatio(); - if (minRatio == RATIO_SYSTEM_DEFAULT) { - minRatio = DEFAULT_MIN_RATIO; - } - float maxRatio = dividerAttributes.getPrimaryMaxRatio(); - if (maxRatio == RATIO_SYSTEM_DEFAULT) { - maxRatio = DEFAULT_MAX_RATIO; + // Update minRatio and maxRatio only when it is a draggable divider. + if (minRatio == RATIO_SYSTEM_DEFAULT) { + minRatio = DEFAULT_MIN_RATIO; + } + if (maxRatio == RATIO_SYSTEM_DEFAULT) { + maxRatio = DEFAULT_MAX_RATIO; + } } return new DividerAttributes.Builder(dividerAttributes) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 32f2d67888ae..a23a47416fb0 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -354,14 +354,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean isolatedNav) { final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( - OP_TYPE_SET_ISOLATED_NAVIGATION).setIsolatedNav(isolatedNav).build(); + OP_TYPE_SET_ISOLATED_NAVIGATION).setBooleanValue(isolatedNav).build(); wct.addTaskFragmentOperation(fragmentToken, operation); } void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean dimOnTask) { final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( - OP_TYPE_SET_DIM_ON_TASK).setDimOnTask(dimOnTask).build(); + OP_TYPE_SET_DIM_ON_TASK).setBooleanValue(dimOnTask).build(); wct.addTaskFragmentOperation(fragmentToken, operation); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 46a3e7f38bed..89d3058cc14d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -67,7 +67,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -113,8 +112,7 @@ import java.util.function.BiConsumer; public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent, DividerPresenter.DragEventCallback { static final String TAG = "SplitController"; - static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); + static final boolean ENABLE_SHELL_TRANSITIONS = true; // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without // association. It's not set in WM Extensions nor Wm Jetpack library currently. diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index de0171de4a37..8aca92e89e6b 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -59,7 +59,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.concurrent.Executor; @@ -74,6 +75,9 @@ import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class DividerPresenterTest { @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Rule public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); private static final int MOCK_TASK_ID = 1234; @@ -118,7 +122,6 @@ public class DividerPresenterTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG); when(mTaskContainer.getTaskId()).thenReturn(MOCK_TASK_ID); @@ -263,6 +266,22 @@ public class DividerPresenterTest { } @Test + public void testSanitizeDividerAttributes_setDefaultValues_fixedDivider() { + DividerAttributes attributes = + new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_FIXED).build(); + DividerAttributes sanitized = DividerPresenter.sanitizeDividerAttributes(attributes); + + assertEquals(DividerAttributes.DIVIDER_TYPE_FIXED, sanitized.getDividerType()); + assertEquals(DividerPresenter.DEFAULT_DIVIDER_WIDTH_DP, sanitized.getWidthDp()); + + // The ratios should not be set for fixed divider + assertEquals(DividerAttributes.RATIO_SYSTEM_DEFAULT, sanitized.getPrimaryMinRatio(), + 0.0f /* delta */); + assertEquals(DividerAttributes.RATIO_SYSTEM_DEFAULT, sanitized.getPrimaryMaxRatio(), + 0.0f /* delta */); + } + + @Test public void testSanitizeDividerAttributes_notChangingValidValues() { DividerAttributes attributes = new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 6f37e9cb794d..76e6a0ff2c21 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -42,10 +42,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; @@ -61,6 +63,9 @@ import java.util.ArrayList; @SmallTest @RunWith(AndroidJUnit4.class) public class JetpackTaskFragmentOrganizerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock private WindowContainerTransaction mTransaction; @Mock @@ -73,7 +78,6 @@ public class JetpackTaskFragmentOrganizerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback); mOrganizer.registerOrganizer(); spyOn(mOrganizer); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index b1b1984e3a70..50abdfd9a66c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -81,7 +81,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; @@ -98,6 +99,8 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class OverlayPresentationTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); @@ -126,7 +129,6 @@ public class OverlayPresentationTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) .getCurrentWindowLayoutInfo(anyInt(), any()); DeviceStateManagerFoldingFeatureProducer producer = diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 3441c2b26ea8..8bc3a300136a 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -111,7 +111,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Collections; @@ -137,6 +138,9 @@ public class SplitControllerTest { new ComponentName("test", "placeholder")); @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Rule public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); private Activity mActivity; @@ -166,7 +170,6 @@ public class SplitControllerTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) .getCurrentWindowLayoutInfo(anyInt(), any()); DeviceStateManagerFoldingFeatureProducer producer = diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 62d8aa30a576..c677484f64f1 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -85,10 +85,12 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; @@ -106,6 +108,10 @@ import java.util.ArrayList; public class SplitPresenterTest { private Activity mActivity; + + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock private Resources mActivityResources; @Mock @@ -119,7 +125,6 @@ public class SplitPresenterTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) .getCurrentWindowLayoutInfo(anyInt(), any()); DeviceStateManagerFoldingFeatureProducer producer = diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index a5995a3027ac..8913b22115e9 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -42,11 +42,12 @@ import android.window.TaskFragmentParentInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.List; @@ -60,14 +61,12 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskContainerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock private SplitController mController; - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - @Test public void testGetWindowingModeForSplitTaskFragment() { final TaskContainer taskContainer = createTestTaskContainer(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java index 379ea0c534ba..a1e9f08585f6 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java @@ -27,10 +27,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** * Test class for {@link TaskFragmentAnimationController}. @@ -42,13 +44,15 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentAnimationControllerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock private TaskFragmentOrganizer mOrganizer; private TaskFragmentAnimationController mAnimationController; @Before public void setup() { - MockitoAnnotations.initMocks(this); mAnimationController = new TaskFragmentAnimationController(mOrganizer); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 0af41791cf4a..abfc9c861d3e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -51,10 +51,12 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl; import com.google.android.collect.Lists; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; @@ -71,6 +73,9 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentContainerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock private SplitPresenter mPresenter; private SplitController mController; @@ -83,7 +88,6 @@ public class TaskFragmentContainerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); DeviceStateManagerFoldingFeatureProducer producer = mock(DeviceStateManagerFoldingFeatureProducer.class); WindowLayoutComponentImpl component = mock(WindowLayoutComponentImpl.class); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java index 459b6d2c31f9..2598dd63bbde 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java @@ -41,10 +41,12 @@ import androidx.test.filters.SmallTest; import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** * Test class for {@link TransactionManager}. @@ -56,6 +58,8 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class TransactionManagerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); @Mock private TaskFragmentOrganizer mOrganizer; @@ -63,7 +67,6 @@ public class TransactionManagerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); mTransactionManager = new TransactionManager(mOrganizer); } diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 5c978e21b9bd..89781fd650a4 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -210,6 +210,7 @@ android_library { "androidx.recyclerview_recyclerview", "kotlinx-coroutines-android", "kotlinx-coroutines-core", + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "//frameworks/libs/systemui:iconloader_base", "com_android_wm_shell_flags_lib", "com.android.window.flags.window-aconfig-java", diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index 0c4fd140780e..ebebd8a52c9a 100644 --- a/libs/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS @@ -1,5 +1,5 @@ xutan@google.com # Give submodule owners in shell resource approval -per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com +per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com per-file res*/*/tv_*.xml = bronger@google.com diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 7b5c471e074f..bcdc2a9c8539 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -62,7 +62,7 @@ <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> von <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> aus <xliff:g id="APP_NAME">%2$s</xliff:g> und <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> weiteren"</string> <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Nach oben links verschieben"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Nach rechts oben verschieben"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Nach oben rechts verschieben"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Nach unten links verschieben"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Nach unten rechts verschieben"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> maximieren"</string> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 89db32793b32..c371f7f62feb 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -61,10 +61,10 @@ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Engadir de novo á pilla"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g> e <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> máis"</string> - <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mover á parte super. esquerda"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover á parte superior dereita"</string> - <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover á parte infer. esquerda"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover á parte inferior dereita"</string> + <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mover arriba á esquerda"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover arriba á dereita"</string> + <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover abaixo á esquerda"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover abaixo á dereita"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"despregar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index e4830af1bad6..a5bd2ab5c10b 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -69,7 +69,7 @@ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> का सेटिङहरू"</string> <string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारेज गर्नुहोस्"</string> - <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"वार्तालाप बबलको रूपमा नदेखाइयोस्"</string> + <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"वार्तालाप बबलको रूपमा नदेखाउनुहोस्"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"बबलहरू प्रयोग गरी कुराकानी गर्नुहोस्"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"नयाँ वार्तालापहरू तैरने आइकन वा बबलका रूपमा देखिन्छन्। बबल खोल्न ट्याप गर्नुहोस्। बबल सार्न सो बबललाई ड्र्याग गर्नुहोस्।"</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"जुनसुकै बेला बबलहरू नियन्त्रण गर्नुहोस्"</string> @@ -99,7 +99,7 @@ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"यो एप तपाईंको स्क्रिनमा अझ राम्रोसँग देखियोस् भन्नाका लागि तपाईं सो एप रिस्टार्ट गर्न सक्नुहुन्छ तर तपाईंले अहिलेसम्म गरेका क्रियाकलाप वा सेभ गर्न बाँकी परिवर्तनहरू हट्न सक्छन्"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द गर्नुहोस्"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string> - <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाइयोस्"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाउनुहोस्"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"यो एप सार्न डबल\nट्याप गर्नुहोस्"</string> <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string> <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index ff77d3b2d150..1210fe8fda05 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -62,9 +62,9 @@ <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> do <xliff:g id="APP_NAME">%2$s</xliff:g> e mais<xliff:g id="BUBBLE_COUNT">%3$d</xliff:g>."</string> <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mover p/ parte sup. esquerda"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover parte superior direita"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover p/ parte sup. direita"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover p/ parte infer. esquerda"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover parte inferior direita"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover p/ parte inf. direita"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expandir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"reduzir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Definições de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index e23c1ff19563..971e146ba77e 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -61,10 +61,10 @@ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Добавить обратно в стек"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> из приложения \"<xliff:g id="APP_NAME">%2$s</xliff:g>\""</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> от приложения \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" и ещё <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g>"</string> - <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Перенести в левый верхний угол"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Перенести в правый верхний угол"</string> - <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Перенести в левый нижний угол"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перенести в правый нижний угол"</string> + <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Переместить в левый верхний угол"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Переместить в правый верхний угол"</string> + <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Переместить в левый нижний угол"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Переместить в правый нижний угол"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Развернуть <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Свернуть <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: настройки"</string> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 407fbbbb1707..fe0b74c469f4 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -64,7 +64,7 @@ <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"ย้ายไปด้านซ้ายบน"</string> <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ย้ายไปด้านขวาบน"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ย้ายไปด้านซ้ายล่าง"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ย้ายไปด้านขาวล่าง"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ย้ายไปด้านขวาล่าง"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ขยาย <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ยุบ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"การตั้งค่า <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl index 526407e25d98..3256abf09116 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl @@ -28,13 +28,14 @@ import com.android.wm.shell.shared.IHomeTransitionListener; interface IShellTransitions { /** - * Registers a remote transition handler. + * Registers a remote transition handler for all operations excluding takeovers (see + * registerRemoteForTakeover()). */ oneway void registerRemote(in TransitionFilter filter, in RemoteTransition remoteTransition) = 1; /** - * Unregisters a remote transition handler. + * Unregisters a remote transition handler for all operations. */ oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2; @@ -52,4 +53,10 @@ interface IShellTransitions { * Returns a container surface for the home root task. */ SurfaceControl getHomeTaskOverlayContainer() = 5; + + /** + * Registers a remote transition for takeover operations only. + */ + oneway void registerRemoteForTakeover(in TransitionFilter filter, + in RemoteTransition remoteTransition) = 6; } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java index 5e49f559ca64..6d4ab4c1bd09 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java @@ -28,13 +28,20 @@ import com.android.wm.shell.shared.annotations.ExternalThread; @ExternalThread public interface ShellTransitions { /** - * Registers a remote transition. + * Registers a remote transition for all operations excluding takeovers (see + * {@link ShellTransitions#registerRemoteForTakeover(TransitionFilter, RemoteTransition)}). */ default void registerRemote(@NonNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition) {} /** - * Unregisters a remote transition. + * Registers a remote transition for takeover operations only. + */ + default void registerRemoteForTakeover(@NonNull TransitionFilter filter, + @NonNull RemoteTransition remoteTransition) {} + + /** + * Unregisters a remote transition for all operations. */ default void unregisterRemote(@NonNull RemoteTransition remoteTransition) {} } 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 d8d0d876b4f2..3244837324b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -16,6 +16,7 @@ package com.android.wm.shell; + import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -30,7 +31,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; -import android.app.AppCompatTaskInfo; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.LocusId; @@ -718,8 +719,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } @Override - public void onCameraControlStateUpdated( - int taskId, @AppCompatTaskInfo.CameraCompatControlState int state) { + public void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state) { final TaskAppearedInfo info; synchronized (mLock) { info = mTasks.get(taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 26432111efdc..196f89d5794e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -49,9 +49,9 @@ public interface BackAnimation { @BackEvent.SwipeEdge int swipeEdge); /** - * Called when the input pointers are pilfered. + * Called when the back swipe threshold is crossed. */ - void onPilferPointers(); + void onThresholdCrossed(); /** * Sets whether the back gesture is past the trigger threshold or not. @@ -101,4 +101,10 @@ public interface BackAnimation { * @param customizer the controller to control system bar color. */ void setStatusBarCustomizer(StatusBarCustomizer customizer); + + /** + * Set a callback to pilfer pointers. + * @param pilferCallback the callback to pilfer pointers. + */ + void setPilferPointerCallback(Runnable pilferCallback); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index d3fe4f82daf7..163a896e2659 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -22,9 +22,6 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -46,7 +43,6 @@ import android.os.UserHandle; import android.provider.Settings.Global; import android.util.DisplayMetrics; import android.util.Log; -import android.util.MathUtils; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -57,6 +53,7 @@ import android.window.BackAnimationAdapter; import android.window.BackEvent; import android.window.BackMotionEvent; import android.window.BackNavigationInfo; +import android.window.BackTouchTracker; import android.window.IBackAnimationFinishedCallback; import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; @@ -118,7 +115,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /** Tracks if we should start the back gesture on the next motion move event */ private boolean mShouldStartOnNextMoveEvent = false; private boolean mOnBackStartDispatched = false; - private boolean mPointerPilfered = false; + private boolean mThresholdCrossed = false; + private boolean mPointersPilfered = false; private final boolean mRequirePointerPilfer; private final FlingAnimationUtils mFlingAnimationUtils; @@ -139,13 +137,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /** * Tracks the current user back gesture. */ - private TouchTracker mCurrentTracker = new TouchTracker(); + private BackTouchTracker mCurrentTracker = new BackTouchTracker(); /** * Tracks the next back gesture in case a new user gesture has started while the back animation * (and navigation) associated with {@link #mCurrentTracker} have not yet finished. */ - private TouchTracker mQueuedTracker = new TouchTracker(); + private BackTouchTracker mQueuedTracker = new BackTouchTracker(); private final Runnable mAnimationTimeoutRunnable = () -> { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...", @@ -189,6 +187,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // Keep previous navigation type before remove mBackNavigationInfo. @BackNavigationInfo.BackTargetType private int mPreviousNavigationType; + private Runnable mPilferPointerCallback; public BackAnimationController( @NonNull ShellInit shellInit, @@ -335,8 +334,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } @Override - public void onPilferPointers() { - BackAnimationController.this.onPilferPointers(); + public void onThresholdCrossed() { + BackAnimationController.this.onThresholdCrossed(); } @Override @@ -358,6 +357,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mCustomizer = customizer; mAnimationBackground.setStatusBarCustomizer(customizer); } + + @Override + public void setPilferPointerCallback(Runnable callback) { + mShellExecutor.execute(() -> { + mPilferPointerCallback = callback; + }); + } } private static class IBackAnimationImpl extends IBackAnimation.Stub @@ -414,20 +420,23 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellBackAnimationRegistry.unregisterAnimation(type); } - private TouchTracker getActiveTracker() { + private BackTouchTracker getActiveTracker() { if (mCurrentTracker.isActive()) return mCurrentTracker; if (mQueuedTracker.isActive()) return mQueuedTracker; return null; } @VisibleForTesting - void onPilferPointers() { - mPointerPilfered = true; + public void onThresholdCrossed() { + mThresholdCrossed = true; // Dispatch onBackStarted, only to app callbacks. // System callbacks will receive onBackStarted when the remote animation starts. - if (!shouldDispatchToAnimator() && mActiveCallback != null) { + final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); + if (!shouldDispatchToAnimator && mActiveCallback != null) { mCurrentTracker.updateStartLocation(); tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null)); + } else if (shouldDispatchToAnimator) { + tryPilferPointers(); } } @@ -443,7 +452,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont int keyAction, @BackEvent.SwipeEdge int swipeEdge) { - TouchTracker activeTouchTracker = getActiveTracker(); + BackTouchTracker activeTouchTracker = getActiveTracker(); if (activeTouchTracker != null) { activeTouchTracker.update(touchX, touchY, velocityX, velocityY); } @@ -487,7 +496,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // onBackCancelled event, let's interrupt it and start animating a new back gesture resetTouchTracker(); } - TouchTracker touchTracker; + BackTouchTracker touchTracker; if (mCurrentTracker.isInInitialState()) { touchTracker = mCurrentTracker; } else if (mQueuedTracker.isInInitialState()) { @@ -498,7 +507,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } touchTracker.setGestureStartLocation(touchX, touchY, swipeEdge); - touchTracker.setState(TouchTracker.TouchTrackerState.ACTIVE); + touchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE); mBackGestureStarted = true; if (interruptCancelPostCommitAnimation) { @@ -514,7 +523,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - private void startBackNavigation(@NonNull TouchTracker touchTracker) { + private void startBackNavigation(@NonNull BackTouchTracker touchTracker) { try { startLatencyTracking(); mBackNavigationInfo = mActivityTaskManager.startBackNavigation( @@ -527,7 +536,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo, - @NonNull TouchTracker touchTracker) { + @NonNull BackTouchTracker touchTracker) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo); if (backNavigationInfo == null) { ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null."); @@ -540,6 +549,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (!mShellBackAnimationRegistry.startGesture(backType)) { mActiveCallback = null; } + tryPilferPointers(); } else { mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); // App is handling back animation. Cancel system animation latency tracking. @@ -588,12 +598,22 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont && mBackNavigationInfo.isPrepareRemoteAnimation(); } + private void tryPilferPointers() { + if (mPointersPilfered || !mThresholdCrossed) { + return; + } + if (mPilferPointerCallback != null) { + mPilferPointerCallback.run(); + } + mPointersPilfered = true; + } + private void tryDispatchOnBackStarted( IOnBackInvokedCallback callback, BackMotionEvent backEvent) { if (mOnBackStartDispatched || callback == null - || (!mPointerPilfered && mRequirePointerPilfer)) { + || (!mThresholdCrossed && mRequirePointerPilfer)) { return; } dispatchOnBackStarted(callback, backEvent); @@ -613,79 +633,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - - /** - * Allows us to manage the fling gesture, it smoothly animates the current progress value to - * the final position, calculated based on the current velocity. - * - * @param callback the callback to be invoked when the animation ends. - */ - private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback, - @NonNull TouchTracker touchTracker) { - if (callback == null) { - return; - } - - boolean animationStarted = false; - - if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) { - - final BackMotionEvent backMotionEvent = touchTracker.createProgressEvent(); - if (backMotionEvent != null) { - // Constraints - absolute values - float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond(); - float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond(); - float maxX = touchTracker.getMaxDistance(); // px - float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px - - // Current state - float currentX = backMotionEvent.getTouchX(); - float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(), - -maxVelocity, maxVelocity); - - // Target state - float animationFaction = velocity / maxVelocity; // value between -1 and 1 - float flingDistance = animationFaction * maxFlingDistance; // px - float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX); - - if (!Float.isNaN(endX) - && currentX != endX - && Math.abs(velocity) >= minVelocity) { - ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX); - - mFlingAnimationUtils.apply( - /* animator = */ animator, - /* currValue = */ currentX, - /* endValue = */ endX, - /* velocity = */ velocity, - /* maxDistance = */ maxFlingDistance - ); - - animator.addUpdateListener(animation -> { - Float animatedValue = (Float) animation.getAnimatedValue(); - float progress = touchTracker.getProgress(animatedValue); - final BackMotionEvent backEvent = touchTracker.createProgressEvent( - progress); - dispatchOnBackProgressed(mActiveCallback, backEvent); - }); - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - dispatchOnBackInvoked(callback); - } - }); - animator.start(); - animationStarted = true; - } - } - } - - if (!animationStarted) { - dispatchOnBackInvoked(callback); - } - } - private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { return; @@ -714,7 +661,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void dispatchOnBackProgressed(IOnBackInvokedCallback callback, BackMotionEvent backEvent) { - if (callback == null) { + if (callback == null || !shouldDispatchToAnimator()) { return; } try { @@ -728,7 +675,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Sets to true when the back gesture has passed the triggering threshold, false otherwise. */ public void setTriggerBack(boolean triggerBack) { - TouchTracker activeBackGestureInfo = getActiveTracker(); + if (mActiveCallback != null) { + try { + mActiveCallback.setTriggerBack(triggerBack); + } catch (RemoteException e) { + Log.e(TAG, "remote setTriggerBack error: ", e); + } + } + BackTouchTracker activeBackGestureInfo = getActiveTracker(); if (activeBackGestureInfo != null) { activeBackGestureInfo.setTriggerBack(triggerBack); } @@ -742,7 +696,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mQueuedTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor); } - private void invokeOrCancelBack(@NonNull TouchTracker touchTracker) { + private void invokeOrCancelBack(@NonNull BackTouchTracker touchTracker) { // Make a synchronized call to core before dispatch back event to client side. // If the close transition happens before the core receives onAnimationFinished, there will // play a second close animation for that transition. @@ -758,7 +712,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mBackNavigationInfo != null) { final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (touchTracker.getTriggerBack()) { - dispatchOrAnimateOnBackInvoked(callback, touchTracker); + dispatchOnBackInvoked(callback); } else { tryDispatchOnBackCancelled(callback); } @@ -770,7 +724,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when the gesture is released, then it could start the post commit animation. */ private void onGestureFinished() { - TouchTracker activeTouchTracker = getActiveTracker(); + BackTouchTracker activeTouchTracker = getActiveTracker(); if (!mBackGestureStarted || activeTouchTracker == null) { // This can happen when an unfinished gesture has been reset in resetTouchTracker ProtoLog.d(WM_SHELL_BACK_PREVIEW, @@ -780,8 +734,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont boolean triggerBack = activeTouchTracker.getTriggerBack(); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", triggerBack); + // Reset gesture states. + mThresholdCrossed = false; + mPointersPilfered = false; mBackGestureStarted = false; - activeTouchTracker.setState(TouchTracker.TouchTrackerState.FINISHED); + activeTouchTracker.setState(BackTouchTracker.TouchTrackerState.FINISHED); if (mPostCommitAnimationInProgress) { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running"); @@ -839,7 +796,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mCurrentTracker.getTriggerBack()) { // notify gesture finished mBackNavigationInfo.onBackGestureFinished(true); - dispatchOrAnimateOnBackInvoked(mActiveCallback, mCurrentTracker); + dispatchOnBackInvoked(mActiveCallback); } else { tryDispatchOnBackCancelled(mActiveCallback); } @@ -882,10 +839,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } /** - * Resets the TouchTracker and potentially starts a new back navigation in case one is queued + * Resets the BackTouchTracker and potentially starts a new back navigation in case one + * is queued. */ private void resetTouchTracker() { - TouchTracker temp = mCurrentTracker; + BackTouchTracker temp = mCurrentTracker; mCurrentTracker = mQueuedTracker; temp.reset(); mQueuedTracker = temp; @@ -928,7 +886,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mApps = null; mShouldStartOnNextMoveEvent = false; mOnBackStartDispatched = false; - mPointerPilfered = false; + mThresholdCrossed = false; + mPointersPilfered = false; mShellBackAnimationRegistry.resetDefaultCrossActivity(); cancelLatencyTracking(); if (mBackNavigationInfo != null) { @@ -1076,7 +1035,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); - pw.println(prefix + " mPointerPilfered=" + mPointerPilfered); + pw.println(prefix + " mPointerPilfered=" + mThresholdCrossed); pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer); pw.println(prefix + " mCurrentTracker state:"); mCurrentTracker.dump(pw, prefix + " "); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 7cb56605cc12..037b1ec9247c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration +import android.graphics.Color import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect @@ -35,6 +36,7 @@ import android.view.animation.DecelerateInterpolator import android.view.animation.Interpolator import android.window.BackEvent import android.window.BackMotionEvent +import android.window.BackNavigationInfo import android.window.BackProgressAnimator import android.window.IOnBackInvokedCallback import com.android.internal.jank.Cuj @@ -67,6 +69,7 @@ class CrossActivityBackAnimation @Inject constructor( private val currentEnteringRect = RectF() private val backAnimRect = Rect() + private val cropRect = Rect() private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) @@ -94,6 +97,12 @@ class CrossActivityBackAnimation @Inject constructor( private var scrimLayer: SurfaceControl? = null private var maxScrimAlpha: Float = 0f + private var isLetterboxed = false + private var enteringHasSameLetterbox = false + private var leftLetterboxLayer: SurfaceControl? = null + private var rightLetterboxLayer: SurfaceControl? = null + private var letterboxColor: Int = 0 + override fun onConfigurationChanged(newConfiguration: Configuration) { cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) } @@ -112,9 +121,19 @@ class CrossActivityBackAnimation @Inject constructor( initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY) transaction.setAnimationTransaction() - + isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed + enteringHasSameLetterbox = isLetterboxed && + closingTarget!!.localBounds.equals(enteringTarget!!.localBounds) + + if (isLetterboxed && !enteringHasSameLetterbox) { + // Play animation with letterboxes, if closing and entering target have mismatching + // letterboxes + backAnimRect.set(closingTarget!!.windowConfiguration.bounds) + } else { + // otherwise play animation on localBounds only + backAnimRect.set(closingTarget!!.localBounds) + } // Offset start rectangle to align task bounds. - backAnimRect.set(closingTarget!!.localBounds) backAnimRect.offsetTo(0, 0) startClosingRect.set(backAnimRect) @@ -136,12 +155,25 @@ class CrossActivityBackAnimation @Inject constructor( targetEnteringRect.set(startEnteringRect) targetEnteringRect.scaleCentered(MAX_SCALE) - // Draw background with task background color. + // Draw background with task background color (or letterbox color). + val backgroundColor = if (isLetterboxed) { + letterboxColor + } else { + enteringTarget!!.taskInfo.taskDescription!!.backgroundColor + } background.ensureBackground( - closingTarget!!.windowConfiguration.bounds, - enteringTarget!!.taskInfo.taskDescription!!.backgroundColor, transaction + closingTarget!!.windowConfiguration.bounds, backgroundColor, transaction ) ensureScrimLayer() + if (isLetterboxed && enteringHasSameLetterbox) { + // crop left and right letterboxes + cropRect.set(closingTarget!!.localBounds.left, 0, closingTarget!!.localBounds.right, + closingTarget!!.windowConfiguration.bounds.height()) + // and add fake letterbox square surfaces instead + ensureLetterboxes() + } else { + cropRect.set(backAnimRect) + } applyTransaction() } @@ -241,17 +273,25 @@ class CrossActivityBackAnimation @Inject constructor( } finishCallback = null removeScrimLayer() + removeLetterbox() + isLetterboxed = false + enteringHasSameLetterbox = false } private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) { if (leash == null || !leash.isValid) return val scale = rect.width() / backAnimRect.width() transformMatrix.reset() - transformMatrix.setScale(scale, scale) + val scalePivotX = if (isLetterboxed && enteringHasSameLetterbox) { + closingTarget!!.localBounds.left.toFloat() + } else { + 0f + } + transformMatrix.setScale(scale, scale, scalePivotX, 0f) transformMatrix.postTranslate(rect.left, rect.top) transaction.setAlpha(leash, alpha) .setMatrix(leash, transformMatrix, tmpFloat9) - .setCrop(leash, backAnimRect) + .setCrop(leash, cropRect) .setCornerRadius(leash, cornerRadius) } @@ -274,24 +314,87 @@ class CrossActivityBackAnimation @Inject constructor( scrimLayer = scrimBuilder.build() val colorComponents = floatArrayOf(0f, 0f, 0f) maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT + val scrimCrop = if (isLetterboxed) { + closingTarget!!.windowConfiguration.bounds + } else { + closingTarget!!.localBounds + } transaction .setColor(scrimLayer, colorComponents) .setAlpha(scrimLayer!!, maxScrimAlpha) - .setCrop(scrimLayer!!, closingTarget!!.localBounds) + .setCrop(scrimLayer!!, scrimCrop) .setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1) .show(scrimLayer) } private fun removeScrimLayer() { - scrimLayer?.let { + if (removeLayer(scrimLayer)) applyTransaction() + scrimLayer = null + } + + /** + * Adds two "fake" letterbox square surfaces to the left and right of the localBounds of the + * closing target + */ + private fun ensureLetterboxes() { + closingTarget?.let { t -> + if (t.localBounds.left != 0 && leftLetterboxLayer == null) { + val bounds = Rect(0, t.windowConfiguration.bounds.top, t.localBounds.left, + t.windowConfiguration.bounds.bottom) + leftLetterboxLayer = ensureLetterbox(bounds) + } + if (t.localBounds.right != t.windowConfiguration.bounds.right && + rightLetterboxLayer == null) { + val bounds = Rect(t.localBounds.right, t.windowConfiguration.bounds.top, + t.windowConfiguration.bounds.right, t.windowConfiguration.bounds.bottom) + rightLetterboxLayer = ensureLetterbox(bounds) + } + } + } + + private fun ensureLetterbox(bounds: Rect): SurfaceControl { + val letterboxBuilder = SurfaceControl.Builder() + .setName("Cross-Activity back animation letterbox") + .setCallsite("CrossActivityBackAnimation") + .setColorLayer() + .setOpaque(true) + .setHidden(false) + + rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, letterboxBuilder) + val layer = letterboxBuilder.build() + val colorComponents = floatArrayOf(Color.red(letterboxColor) / 255f, + Color.green(letterboxColor) / 255f, Color.blue(letterboxColor) / 255f) + transaction + .setColor(layer, colorComponents) + .setCrop(layer, bounds) + .setRelativeLayer(layer, closingTarget!!.leash, 1) + .show(layer) + return layer + } + + private fun removeLetterbox() { + if (removeLayer(leftLetterboxLayer) || removeLayer(rightLetterboxLayer)) applyTransaction() + leftLetterboxLayer = null + rightLetterboxLayer = null + } + + private fun removeLayer(layer: SurfaceControl?): Boolean { + layer?.let { if (it.isValid) { transaction.remove(it) - applyTransaction() + return true } } - scrimLayer = null + return false } + override fun prepareNextAnimation( + animationInfo: BackNavigationInfo.CustomAnimationInfo?, + letterboxColor: Int + ): Boolean { + this.letterboxColor = letterboxColor + return false + } private inner class Callback : IOnBackInvokedCallback.Default() { override fun onBackStarted(backMotionEvent: BackMotionEvent) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index 838dab43d6e8..e27b40e58591 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -271,7 +271,8 @@ public class CustomizeActivityAnimation extends ShellBackAnimation { /** Load customize animation before animation start. */ @Override - public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { + public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo, + int letterboxColor) { if (animationInfo == null) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java index 8a0daaa72e24..9cd193b0f74c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java @@ -42,11 +42,12 @@ public abstract class ShellBackAnimation { public abstract BackAnimationRunner getRunner(); /** - * Prepare the next animation with customized animation. + * Prepare the next animation. * * @return true if this type of back animation should override the default. */ - public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { + public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo, + int letterboxColor) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java index 7a6032c60cce..6fafa75e2f70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java @@ -154,11 +154,14 @@ public class ShellBackAnimationRegistry { if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) { if (mCustomizeActivityAnimation != null && mCustomizeActivityAnimation.prepareNextAnimation( - backNavigationInfo.getCustomAnimationInfo())) { + backNavigationInfo.getCustomAnimationInfo(), 0)) { mAnimationDefinition.get(type).resetWaitingAnimation(); mAnimationDefinition.set( BackNavigationInfo.TYPE_CROSS_ACTIVITY, mCustomizeActivityAnimation.getRunner()); + } else if (mDefaultCrossActivityAnimation != null) { + mDefaultCrossActivityAnimation.prepareNextAnimation(null, + backNavigationInfo.getLetterboxColor()); } } BackAnimationRunner runner = mAnimationDefinition.get(type); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java deleted file mode 100644 index 8f04f126960c..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) 2022 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.back; - -import android.annotation.FloatRange; -import android.os.SystemProperties; -import android.util.MathUtils; -import android.view.MotionEvent; -import android.view.RemoteAnimationTarget; -import android.window.BackEvent; -import android.window.BackMotionEvent; - -import java.io.PrintWriter; - -/** - * Helper class to record the touch location for gesture and generate back events. - */ -class TouchTracker { - private static final String PREDICTIVE_BACK_LINEAR_DISTANCE_PROP = - "persist.wm.debug.predictive_back_linear_distance"; - private static final int LINEAR_DISTANCE = SystemProperties - .getInt(PREDICTIVE_BACK_LINEAR_DISTANCE_PROP, -1); - private float mLinearDistance = LINEAR_DISTANCE; - private float mMaxDistance; - private float mNonLinearFactor; - /** - * Location of the latest touch event - */ - private float mLatestTouchX; - private float mLatestTouchY; - private boolean mTriggerBack; - - /** - * Location of the initial touch event of the back gesture. - */ - private float mInitTouchX; - private float mInitTouchY; - private float mLatestVelocityX; - private float mLatestVelocityY; - private float mStartThresholdX; - private int mSwipeEdge; - private TouchTrackerState mState = TouchTrackerState.INITIAL; - - void update(float touchX, float touchY, float velocityX, float velocityY) { - /** - * If back was previously cancelled but the user has started swiping in the forward - * direction again, restart back. - */ - if ((touchX < mStartThresholdX && mSwipeEdge == BackEvent.EDGE_LEFT) - || (touchX > mStartThresholdX && mSwipeEdge == BackEvent.EDGE_RIGHT)) { - mStartThresholdX = touchX; - if ((mSwipeEdge == BackEvent.EDGE_LEFT && mStartThresholdX < mInitTouchX) - || (mSwipeEdge == BackEvent.EDGE_RIGHT && mStartThresholdX > mInitTouchX)) { - mInitTouchX = mStartThresholdX; - } - } - mLatestTouchX = touchX; - mLatestTouchY = touchY; - mLatestVelocityX = velocityX; - mLatestVelocityY = velocityY; - } - - void setTriggerBack(boolean triggerBack) { - if (mTriggerBack != triggerBack && !triggerBack) { - mStartThresholdX = mLatestTouchX; - } - mTriggerBack = triggerBack; - } - - boolean getTriggerBack() { - return mTriggerBack; - } - - void setState(TouchTrackerState state) { - mState = state; - } - - boolean isInInitialState() { - return mState == TouchTrackerState.INITIAL; - } - - boolean isActive() { - return mState == TouchTrackerState.ACTIVE; - } - - boolean isFinished() { - return mState == TouchTrackerState.FINISHED; - } - - void setGestureStartLocation(float touchX, float touchY, int swipeEdge) { - mInitTouchX = touchX; - mInitTouchY = touchY; - mLatestTouchX = touchX; - mLatestTouchY = touchY; - mSwipeEdge = swipeEdge; - mStartThresholdX = mInitTouchX; - } - - /** Update the start location used to compute the progress - * to the latest touch location. - */ - void updateStartLocation() { - mInitTouchX = mLatestTouchX; - mInitTouchY = mLatestTouchY; - mStartThresholdX = mInitTouchX; - } - - void reset() { - mInitTouchX = 0; - mInitTouchY = 0; - mStartThresholdX = 0; - mTriggerBack = false; - mState = TouchTrackerState.INITIAL; - mSwipeEdge = BackEvent.EDGE_LEFT; - } - - BackMotionEvent createStartEvent(RemoteAnimationTarget target) { - return new BackMotionEvent( - /* touchX = */ mInitTouchX, - /* touchY = */ mInitTouchY, - /* progress = */ 0, - /* velocityX = */ 0, - /* velocityY = */ 0, - /* triggerBack = */ mTriggerBack, - /* swipeEdge = */ mSwipeEdge, - /* departingAnimationTarget = */ target); - } - - BackMotionEvent createProgressEvent() { - float progress = getProgress(mLatestTouchX); - return createProgressEvent(progress); - } - - /** - * Progress value computed from the touch position. - * - * @param touchX the X touch position of the {@link MotionEvent}. - * @return progress value - */ - @FloatRange(from = 0.0, to = 1.0) - float getProgress(float touchX) { - // If back is committed, progress is the distance between the last and first touch - // point, divided by the max drag distance. Otherwise, it's the distance between - // the last touch point and the starting threshold, divided by max drag distance. - // The starting threshold is initially the first touch location, and updated to - // the location everytime back is restarted after being cancelled. - float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; - float distance; - if (mSwipeEdge == BackEvent.EDGE_LEFT) { - distance = touchX - startX; - } else { - distance = startX - touchX; - } - float deltaX = Math.max(0f, distance); - float linearDistance = mLinearDistance; - float maxDistance = getMaxDistance(); - maxDistance = maxDistance == 0 ? 1 : maxDistance; - float progress; - if (linearDistance < maxDistance) { - // Up to linearDistance it behaves linearly, then slowly reaches 1f. - - // maxDistance is composed of linearDistance + nonLinearDistance - float nonLinearDistance = maxDistance - linearDistance; - float initialTarget = linearDistance + nonLinearDistance * mNonLinearFactor; - - boolean isLinear = deltaX <= linearDistance; - if (isLinear) { - progress = deltaX / initialTarget; - } else { - float nonLinearDeltaX = deltaX - linearDistance; - float nonLinearProgress = nonLinearDeltaX / nonLinearDistance; - float currentTarget = MathUtils.lerp( - /* start = */ initialTarget, - /* stop = */ maxDistance, - /* amount = */ nonLinearProgress); - progress = deltaX / currentTarget; - } - } else { - // Always linear behavior. - progress = deltaX / maxDistance; - } - return MathUtils.constrain(progress, 0, 1); - } - - /** - * Maximum distance in pixels. - * Progress is considered to be completed (1f) when this limit is exceeded. - */ - float getMaxDistance() { - return mMaxDistance; - } - - BackMotionEvent createProgressEvent(float progress) { - return new BackMotionEvent( - /* touchX = */ mLatestTouchX, - /* touchY = */ mLatestTouchY, - /* progress = */ progress, - /* velocityX = */ mLatestVelocityX, - /* velocityY = */ mLatestVelocityY, - /* triggerBack = */ mTriggerBack, - /* swipeEdge = */ mSwipeEdge, - /* departingAnimationTarget = */ null); - } - - public void setProgressThresholds(float linearDistance, float maxDistance, - float nonLinearFactor) { - if (LINEAR_DISTANCE >= 0) { - mLinearDistance = LINEAR_DISTANCE; - } else { - mLinearDistance = linearDistance; - } - mMaxDistance = maxDistance; - mNonLinearFactor = nonLinearFactor; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "TouchTracker state:"); - pw.println(prefix + " mState=" + mState); - pw.println(prefix + " mTriggerBack=" + mTriggerBack); - } - - enum TouchTrackerState { - INITIAL, ACTIVE, FINISHED - } - -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index a67821b7e819..0297901c8921 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -288,13 +288,16 @@ public class BadgedImageView extends ConstraintLayout { /** Sets the position of the dot and badge, animating them out and back in if requested. */ void animateDotBadgePositions(boolean onLeft) { - mOnLeft = onLeft; - - if (onLeft != getDotOnLeft() && shouldDrawDot()) { - animateDotScale(0f /* showDot */, () -> { - invalidate(); - animateDotScale(1.0f, null /* after */); - }); + if (onLeft != getDotOnLeft()) { + if (shouldDrawDot()) { + animateDotScale(0f /* showDot */, () -> { + mOnLeft = onLeft; + invalidate(); + animateDotScale(1.0f, null /* after */); + }); + } else { + mOnLeft = onLeft; + } } // TODO animate badge showBadge(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 74f087b6d8f8..4e8afccee40f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -68,6 +68,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.common.TriangleShape; @@ -446,6 +447,8 @@ public class BubbleExpandedView extends LinearLayout { mManageButton.setVisibility(GONE); } else { mTaskView = bubbleTaskView.getTaskView(); + // reset the insets that might left after TaskView is shown in BubbleBarExpandedView + mTaskView.setCaptionInsets(null); bubbleTaskView.setDelegateListener(mTaskViewListener); // set a fixed width so it is not recalculated as part of a rotation. the width will be @@ -668,6 +671,11 @@ public class BubbleExpandedView extends LinearLayout { } } + /** Sets the alpha for the pointer. */ + public void setPointerAlpha(float alpha) { + mPointerView.setAlpha(alpha); + } + /** * Get alpha from underlying {@code TaskView} if this view is for a bubble. * Or get alpha for the overflow view if this view is for overflow. @@ -698,12 +706,14 @@ public class BubbleExpandedView extends LinearLayout { } } - /** - * Sets the alpha of the background and the pointer view. - */ + /** Sets the alpha of the background. */ public void setBackgroundAlpha(float alpha) { - mPointerView.setAlpha(alpha); - setAlpha(alpha); + if (Flags.enableNewBubbleAnimations()) { + setAlpha(alpha); + } else { + mPointerView.setAlpha(alpha); + setAlpha(alpha); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 8da85d2d6abf..dcc536b5b043 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2358,9 +2358,9 @@ public class BubbleStackView extends FrameLayout } else { index = getBubbleIndex(mExpandedBubble); } - PointF p = mPositioner.getExpandedBubbleXY(index, getState()); + PointF bubbleXY = mPositioner.getExpandedBubbleXY(index, getState()); final float translationY = mPositioner.getExpandedViewY(mExpandedBubble, - mPositioner.showBubblesVertically() ? p.y : p.x); + mPositioner.showBubblesVertically() ? bubbleXY.y : bubbleXY.x); mExpandedViewContainer.setTranslationX(0f); mExpandedViewContainer.setTranslationY(translationY); mExpandedViewContainer.setAlpha(1f); @@ -2371,40 +2371,42 @@ public class BubbleStackView extends FrameLayout ? mStackAnimationController.getStackPosition().y : mStackAnimationController.getStackPosition().x; final float bubbleWillBeAt = showVertically - ? p.y - : p.x; + ? bubbleXY.y + : bubbleXY.x; final float distanceAnimated = Math.abs(bubbleWillBeAt - relevantStackPosition); // Wait for the path animation target to reach its end, and add a small amount of extra time // if the bubble is moving a lot horizontally. - long startDelay = 0L; + final long startDelay; // Should not happen since we lay out before expanding, but just in case... if (getWidth() > 0) { startDelay = (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 1.2f + (distanceAnimated / getWidth()) * 30); + } else { + startDelay = 0L; } // Set the pivot point for the scale, so the expanded view animates out from the bubble. if (showVertically) { float pivotX; if (mStackOnLeftOrWillBe) { - pivotX = p.x + mBubbleSize + mExpandedViewPadding; + pivotX = bubbleXY.x + mBubbleSize + mExpandedViewPadding; } else { - pivotX = p.x - mExpandedViewPadding; + pivotX = bubbleXY.x - mExpandedViewPadding; } mExpandedViewContainerMatrix.setScale( 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, pivotX, - p.y + mBubbleSize / 2f); + bubbleXY.y + mBubbleSize / 2f); } else { mExpandedViewContainerMatrix.setScale( 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, - p.x + mBubbleSize / 2f, - p.y + mBubbleSize + mExpandedViewPadding); + bubbleXY.x + mBubbleSize / 2f, + bubbleXY.y + mBubbleSize + mExpandedViewPadding); } mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index b87c2f6ebad5..7ceaaea3962f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -125,6 +125,7 @@ public class PipBoundsState { private @Nullable Runnable mOnMinimalSizeChangeCallback; private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); + private List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>(); // the size of the current bounds relative to the max size spec private float mBoundsScale; @@ -297,7 +298,12 @@ public class PipBoundsState { /** Set the PIP aspect ratio. */ public void setAspectRatio(float aspectRatio) { - mAspectRatio = aspectRatio; + if (Float.compare(mAspectRatio, aspectRatio) != 0) { + mAspectRatio = aspectRatio; + for (Consumer<Float> callback : mOnAspectRatioChangedCallbacks) { + callback.accept(mAspectRatio); + } + } } /** Get the PIP aspect ratio. */ @@ -527,6 +533,23 @@ public class PipBoundsState { mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback); } + /** Adds callback to listen on aspect ratio change. */ + public void addOnAspectRatioChangedCallback( + @NonNull Consumer<Float> onAspectRatioChangedCallback) { + if (!mOnAspectRatioChangedCallbacks.contains(onAspectRatioChangedCallback)) { + mOnAspectRatioChangedCallbacks.add(onAspectRatioChangedCallback); + onAspectRatioChangedCallback.accept(mAspectRatio); + } + } + + /** Removes callback to listen on aspect ratio change. */ + public void removeOnAspectRatioChangedCallback( + @NonNull Consumer<Float> onAspectRatioChangedCallback) { + if (mOnAspectRatioChangedCallbacks.contains(onAspectRatioChangedCallback)) { + mOnAspectRatioChangedCallbacks.remove(onAspectRatioChangedCallback); + } + } + public LauncherState getLauncherState() { return mLauncherState; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index dae62ac74483..30eb8b5d2f05 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -18,6 +18,7 @@ package com.android.wm.shell.common.split; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; 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_APPLICATION_OVERLAY; @@ -109,7 +110,7 @@ public class SplitDecorManager extends WindowlessWindowManager { } /** Inflates split decor surface on the root surface. */ - public void inflate(Context context, SurfaceControl rootLeash, Rect rootBounds) { + public void inflate(Context context, SurfaceControl rootLeash) { if (mIconLeash != null && mViewHost != null) { return; } @@ -128,13 +129,12 @@ public class SplitDecorManager extends WindowlessWindowManager { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); - lp.width = rootBounds.width(); - lp.height = rootBounds.height(); + lp.width = mIconSize; + lp.height = mIconSize; lp.token = new Binder(); lp.setTitle(TAG); lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports - // TRUSTED_OVERLAY for windowless window without input channel. + lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL; mViewHost.setView(rootLayout, lp); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 86571cf9c622..5c292f173e5b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.AppCompatTaskInfo.CameraCompatControlState; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java index a0986fa601f2..2b0bd3272ed2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java @@ -16,10 +16,10 @@ package com.android.wm.shell.compatui; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import android.annotation.IdRes; -import android.app.AppCompatTaskInfo.CameraCompatControlState; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.content.Context; import android.util.AttributeSet; import android.view.View; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index dbf7186def8a..4e5c2fa24f25 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -16,16 +16,16 @@ package com.android.wm.shell.compatui; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppCompatTaskInfo; -import android.app.AppCompatTaskInfo.CameraCompatControlState; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.Context; import android.graphics.Rect; @@ -81,7 +81,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { super(context, taskInfo, syncQueue, taskListener, displayLayout); mCallback = callback; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; - mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState; + mCameraCompatControlState = + taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState; mCompatUIHintsState = compatUIHintsState; mCompatUIConfiguration = compatUIConfiguration; mOnRestartButtonClicked = onRestartButtonClicked; @@ -135,7 +136,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; - mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState; + mCameraCompatControlState = + taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState; if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) { return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index 7c280994042b..8fb4bdbea933 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -237,7 +237,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract final int letterboxWidth = taskInfo.topActivityLetterboxWidth; // App is not visibly letterboxed if it covers status bar/bottom insets or matches the // stable bounds, so don't show the button - if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) { + if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth + && !taskInfo.isUserFullscreenOverrideEnabled) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 73228de83c0f..6834e6d3123f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -107,6 +107,7 @@ import com.android.wm.shell.taskview.TaskViewFactory; import com.android.wm.shell.taskview.TaskViewFactoryController; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.HomeTransitionObserver; +import com.android.wm.shell.transition.MixedTransitionHandler; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldAnimationController; @@ -675,6 +676,22 @@ public abstract class WMShellBaseModule { return new TaskViewTransitions(transitions); } + // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} + @BindsOptionalOf + @DynamicOverride + abstract MixedTransitionHandler optionalMixedTransitionHandler(); + + @WMSingleton + @Provides + static Optional<MixedTransitionHandler> provideMixedTransitionHandler( + @DynamicOverride Optional<MixedTransitionHandler> mixedTransitionHandler + ) { + if (mixedTransitionHandler.isPresent()) { + return mixedTransitionHandler; + } + return Optional.empty(); + } + // // Keyguard transitions (optional feature) // @@ -934,6 +951,7 @@ public abstract class WMShellBaseModule { Optional<OneHandedController> oneHandedControllerOptional, Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional, Optional<ActivityEmbeddingController> activityEmbeddingOptional, + Optional<MixedTransitionHandler> mixedTransitionHandler, Transitions transitions, StartingWindowController startingWindow, ProtoLogController protoLogController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 1408eadf544e..b574b8159307 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -29,6 +29,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.IconProvider; +import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -59,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver; import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; @@ -85,6 +87,7 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.HomeTransitionObserver; +import com.android.wm.shell.transition.MixedTransitionHandler; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldAnimationController; @@ -371,8 +374,9 @@ public abstract class WMShellModule { // @WMSingleton + @DynamicOverride @Provides - static DefaultMixedHandler provideDefaultMixedHandler( + static MixedTransitionHandler provideMixedTransitionHandler( ShellInit shellInit, Optional<SplitScreenController> splitScreenOptional, @Nullable PipTransitionController pipTransitionController, @@ -517,23 +521,39 @@ public abstract class WMShellModule { LaunchAdjacentController launchAdjacentController, RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, - @ShellMainThread ShellExecutor mainExecutor - ) { + @ShellMainThread ShellExecutor mainExecutor, + Optional<DesktopTasksLimiter> desktopTasksLimiter) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, - recentsTransitionHandler, multiInstanceHelper, mainExecutor); + recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter); + } + + @WMSingleton + @Provides + static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter( + Transitions transitions, + @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + ShellTaskOrganizer shellTaskOrganizer) { + if (!DesktopModeStatus.isEnabled() || !Flags.enableDesktopWindowingTaskLimit()) { + return Optional.empty(); + } + return Optional.of( + new DesktopTasksLimiter( + transitions, desktopModeTaskRepository, shellTaskOrganizer)); } + @WMSingleton @Provides static DragToDesktopTransitionHandler provideDragToDesktopTransitionHandler( Context context, Transitions transitions, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + Optional<DesktopTasksLimiter> desktopTasksLimiter) { return new DragToDesktopTransitionHandler(context, transitions, rootTaskDisplayAreaOrganizer); } @@ -541,7 +561,8 @@ public abstract class WMShellModule { @WMSingleton @Provides static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler( - Transitions transitions) { + Transitions transitions, + Optional<DesktopTasksLimiter> desktopTasksLimiter) { return new EnterDesktopTaskTransitionHandler(transitions); } @@ -636,7 +657,6 @@ public abstract class WMShellModule { @Provides static Object provideIndependentShellComponentsToCreate( DragAndDropController dragAndDropController, - DefaultMixedHandler defaultMixedHandler, Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) { return new Object(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 32c22c01a828..fcddcad3a949 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -77,6 +77,22 @@ public class DesktopModeStatus { "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); /** + * Default value for {@code MAX_TASK_LIMIT}. + */ + @VisibleForTesting + public static final int DEFAULT_MAX_TASK_LIMIT = 4; + + // TODO(b/335131008): add a config-overlay field for the max number of tasks in Desktop Mode + /** + * Flag declaring the maximum number of Tasks to show in Desktop Mode at any one time. + * + * <p> The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen + * recording window, or Bluetooth pairing window). + */ + private static final int MAX_TASK_LIMIT = SystemProperties.getInt( + "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT); + + /** * Return {@code true} if desktop windowing is enabled */ public static boolean isEnabled() { @@ -124,6 +140,13 @@ public class DesktopModeStatus { } /** + * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time. + */ + static int getMaxTaskLimit() { + return MAX_TASK_LIMIT; + } + + /** * Return {@code true} if the current device supports desktop mode. */ @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 50cea01fa281..2d508b2e6e3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -47,6 +47,7 @@ class DesktopModeTaskRepository { */ val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), + val minimizedTasks: ArraySet<Int> = ArraySet(), var stashed: Boolean = false ) @@ -202,6 +203,13 @@ class DesktopModeTaskRepository { } } + /** Return whether the given Task is minimized. */ + fun isMinimizedTask(taskId: Int): Boolean { + return displayData.valueIterator().asSequence().any { data -> + data.minimizedTasks.contains(taskId) + } + } + /** * Check if a task with the given [taskId] is the only active task on its display */ @@ -219,6 +227,25 @@ class DesktopModeTaskRepository { } /** + * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks + * are visible. + */ + fun isDesktopModeShowing(displayId: Int): Boolean = getVisibleTaskCount(displayId) > 0 + + /** + * Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display, + * ordered from front to back. + */ + fun getActiveNonMinimizedTasksOrderedFrontToBack(displayId: Int): List<Int> { + val activeTasks = getActiveTasks(displayId) + val allTasksInZOrder = getFreeformTasksInZOrder() + return activeTasks + // Don't show already minimized Tasks + .filter { taskId -> !isMinimizedTask(taskId) } + .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) } + } + + /** * Get a list of freeform tasks, ordered from top-bottom (top at index 0). */ // TODO(b/278084491): pass in display id @@ -255,6 +282,7 @@ class DesktopModeTaskRepository { val prevCount = getVisibleTaskCount(displayId) if (visible) { displayData.getOrCreate(displayId).visibleTasks.add(taskId) + unminimizeTask(displayId, taskId) } else { displayData[displayId]?.visibleTasks?.remove(taskId) } @@ -312,6 +340,24 @@ class DesktopModeTaskRepository { freeformTasksInZOrder.add(0, taskId) } + /** Mark a Task as minimized. */ + fun minimizeTask(displayId: Int, taskId: Int) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopModeTaskRepository: minimize Task: display=%d, task=%d", + displayId, taskId) + displayData.getOrCreate(displayId).minimizedTasks.add(taskId) + } + + /** Mark a Task as non-minimized. */ + fun unminimizeTask(displayId: Int, taskId: Int) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d", + displayId, taskId) + displayData[displayId]?.minimizedTasks?.remove(taskId) + } + /** * Remove the task from the ordered list. */ @@ -325,7 +371,7 @@ class DesktopModeTaskRepository { boundsBeforeMaximizeByTaskId.remove(taskId) KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString() + "DesktopTaskRepo: remaining freeform tasks: %s", freeformTasksInZOrder.toDumpString(), ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 068661a6a666..0a9e5d0d5345 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -88,6 +88,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import com.android.wm.shell.windowdecor.extension.isFreeform import com.android.wm.shell.windowdecor.extension.isFullscreen import java.io.PrintWriter +import java.util.Optional import java.util.concurrent.Executor import java.util.function.Consumer @@ -113,7 +114,8 @@ class DesktopTasksController( private val launchAdjacentController: LaunchAdjacentController, private val recentsTransitionHandler: RecentsTransitionHandler, private val multiInstanceHelper: MultiInstanceHelper, - @ShellMainThread private val mainExecutor: ShellExecutor + @ShellMainThread private val mainExecutor: ShellExecutor, + private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, DragAndDropController.DragAndDropListener { @@ -341,11 +343,13 @@ class DesktopTasksController( ) exitSplitIfApplicable(wct, task) // Bring other apps to front first - bringDesktopAppsToFront(task.displayId, wct) + val taskToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) addMoveToDesktopChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.moveToDesktop(wct) + val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct) + addPendingMinimizeTransition(transition, taskToMinimize) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -382,10 +386,14 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() exitSplitIfApplicable(wct, taskInfo) - bringDesktopAppsToFront(taskInfo.displayId, wct) + moveHomeTaskToFront(wct) + val taskToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask( + taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) - dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) + val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) + transition?.let { addPendingMinimizeTransition(it, taskToMinimize) } } /** @@ -507,8 +515,10 @@ class DesktopTasksController( val wct = WindowContainerTransaction() wct.reorder(taskInfo.token, true) + val taskToMinimize = addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */) + val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */) + addPendingMinimizeTransition(transition, taskToMinimize) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -688,9 +698,20 @@ class DesktopTasksController( ?: WINDOWING_MODE_UNDEFINED } - private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront") - val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId) + private fun bringDesktopAppsToFrontBeforeShowingNewTask( + displayId: Int, + wct: WindowContainerTransaction, + newTaskIdInFront: Int + ): RunningTaskInfo? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront) + + private fun bringDesktopAppsToFront( + displayId: Int, + wct: WindowContainerTransaction, + newTaskIdInFront: Int? = null + ): RunningTaskInfo? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s", + newTaskIdInFront ?: "null") if (Flags.enableDesktopWindowingWallpaperActivity()) { // Add translucent wallpaper activity to show the wallpaper underneath @@ -700,13 +721,21 @@ class DesktopTasksController( moveHomeTaskToFront(wct) } - // Then move other tasks on top of it - val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder() - activeTasks - // Sort descending as the top task is at index 0. It should be ordered to top last - .sortedByDescending { taskId -> allTasksInZOrder.indexOf(taskId) } - .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) } - .forEach { task -> wct.reorder(task.token, true /* onTop */) } + val nonMinimizedTasksOrderedFrontToBack = + desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId) + // If we're adding a new Task we might need to minimize an old one + val taskToMinimize: RunningTaskInfo? = + if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) { + desktopTasksLimiter.get().getTaskToMinimizeIfNeeded( + nonMinimizedTasksOrderedFrontToBack, newTaskIdInFront) + } else { null } + nonMinimizedTasksOrderedFrontToBack + // If there is a Task to minimize, let it stay behind the Home Task + .filter { taskId -> taskId != taskToMinimize?.taskId } + .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) } + .reversed() // Start from the back so the front task is brought forward last + .forEach { task -> wct.reorder(task.token, true /* onTop */) } + return taskToMinimize } private fun moveHomeTaskToFront(wct: WindowContainerTransaction) { @@ -824,13 +853,13 @@ class DesktopTasksController( when { request.type == TRANSIT_TO_BACK -> handleBackNavigation(task) // If display has tasks stashed, handle as stashed launch - task.isStashed -> handleStashedTaskLaunch(task) + task.isStashed -> handleStashedTaskLaunch(task, transition) // Check if the task has a top transparent activity shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task) // Check if fullscreen task should be updated - task.isFullscreen -> handleFullscreenTaskLaunch(task) + task.isFullscreen -> handleFullscreenTaskLaunch(task, transition) // Check if freeform task should be updated - task.isFreeform -> handleFreeformTaskLaunch(task) + task.isFreeform -> handleFreeformTaskLaunch(task, transition) else -> { null } @@ -878,10 +907,12 @@ class DesktopTasksController( } ?: false } - private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + private fun handleFreeformTaskLaunch( + task: RunningTaskInfo, + transition: IBinder + ): WindowContainerTransaction? { KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") - val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) - if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { + if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) { KtProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: switch freeform task to fullscreen oon transition" + @@ -892,13 +923,23 @@ class DesktopTasksController( addMoveToFullscreenChanges(wct, task) } } + // Desktop Mode is showing and we're launching a new Task - we might need to minimize + // a Task. + val wct = WindowContainerTransaction() + val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) + if (taskToMinimize != null) { + addPendingMinimizeTransition(transition, taskToMinimize) + return wct + } return null } - private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + private fun handleFullscreenTaskLaunch( + task: RunningTaskInfo, + transition: IBinder + ): WindowContainerTransaction? { KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch") - val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) - if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { + if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) { KtProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: switch fullscreen task to freeform on transition" + @@ -907,21 +948,30 @@ class DesktopTasksController( ) return WindowContainerTransaction().also { wct -> addMoveToDesktopChanges(wct, task) + // Desktop Mode is already showing and we're launching a new Task - we might need to + // minimize another Task. + val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) + addPendingMinimizeTransition(transition, taskToMinimize) } } return null } - private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction { + private fun handleStashedTaskLaunch( + task: RunningTaskInfo, + transition: IBinder + ): WindowContainerTransaction { KtProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: launch apps with stashed on transition taskId=%d", task.taskId ) val wct = WindowContainerTransaction() - bringDesktopAppsToFront(task.displayId, wct) + val taskToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) addMoveToDesktopChanges(wct, task) desktopModeTaskRepository.setStashed(task.displayId, false) + addPendingMinimizeTransition(transition, taskToMinimize) return wct } @@ -1002,6 +1052,28 @@ class DesktopTasksController( wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) } + /** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */ + private fun addAndGetMinimizeChangesIfNeeded( + displayId: Int, + wct: WindowContainerTransaction, + newTaskInfo: RunningTaskInfo + ): RunningTaskInfo? { + if (!desktopTasksLimiter.isPresent) return null + return desktopTasksLimiter.get().addAndGetMinimizeTaskChangesIfNeeded( + displayId, wct, newTaskInfo) + } + + private fun addPendingMinimizeTransition( + transition: IBinder, + taskToMinimize: RunningTaskInfo? + ) { + if (taskToMinimize == null) return + desktopTasksLimiter.ifPresent { + it.addPendingMinimizeChange( + transition, taskToMinimize.displayId, taskToMinimize.taskId) + } + } + /** Enter split by using the focused desktop task in given `displayId`. */ fun enterSplit( displayId: Int, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt new file mode 100644 index 000000000000..3404d376fe92 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.os.IBinder +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_TO_BACK +import android.window.TransitionInfo +import android.window.WindowContainerTransaction +import androidx.annotation.VisibleForTesting +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionObserver +import com.android.wm.shell.util.KtProtoLog + +/** + * Limits the number of tasks shown in Desktop Mode. + * + * This class should only be used if + * [com.android.window.flags.Flags.enableDesktopWindowingTaskLimit()] is true. + */ +class DesktopTasksLimiter ( + transitions: Transitions, + private val taskRepository: DesktopModeTaskRepository, + private val shellTaskOrganizer: ShellTaskOrganizer, +) { + private val minimizeTransitionObserver = MinimizeTransitionObserver() + + init { + transitions.registerObserver(minimizeTransitionObserver) + } + + private data class TaskDetails (val displayId: Int, val taskId: Int) + + // TODO(b/333018485): replace this observer when implementing the minimize-animation + private inner class MinimizeTransitionObserver : TransitionObserver { + private val mPendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + + fun addPendingTransitionToken(transition: IBinder, taskDetails: TaskDetails) { + mPendingTransitionTokensAndTasks[transition] = taskDetails + } + + override fun onTransitionReady( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction + ) { + val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return + + if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return + + if (!isTaskReorderedToBackOrInvisible(info, taskToMinimize)) { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: task %d is not reordered to back nor invis", + taskToMinimize.taskId) + return + } + this@DesktopTasksLimiter.markTaskMinimized( + taskToMinimize.displayId, taskToMinimize.taskId) + } + + /** + * Returns whether the given Task is being reordered to the back in the given transition, or + * is already invisible. + * + * <p> This check can be used to double-check that a task was indeed minimized before + * marking it as such. + */ + private fun isTaskReorderedToBackOrInvisible( + info: TransitionInfo, + taskDetails: TaskDetails + ): Boolean { + val taskChange = info.changes.find { change -> + change.taskInfo?.taskId == taskDetails.taskId } + if (taskChange == null) { + return !taskRepository.isVisibleTask(taskDetails.taskId) + } + return taskChange.mode == TRANSIT_TO_BACK + } + + override fun onTransitionStarting(transition: IBinder) {} + + override fun onTransitionMerged(merged: IBinder, playing: IBinder) { + mPendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> + mPendingTransitionTokensAndTasks[playing] = taskToTransfer + } + } + + override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: transition %s finished", transition) + mPendingTransitionTokensAndTasks.remove(transition) + } + } + + /** + * Mark a task as minimized, this should only be done after the corresponding transition has + * finished so we don't minimize the task if the transition fails. + */ + private fun markTaskMinimized(displayId: Int, taskId: Int) { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: marking %d as minimized", taskId) + taskRepository.minimizeTask(displayId, taskId) + } + + /** + * Add a minimize-transition to [wct] if adding [newFrontTaskInfo] brings us over the task + * limit. + * + * @param transition the transition that the minimize-transition will be appended to, or null if + * the transition will be started later. + * @return the ID of the minimized task, or null if no task is being minimized. + */ + fun addAndGetMinimizeTaskChangesIfNeeded( + displayId: Int, + wct: WindowContainerTransaction, + newFrontTaskInfo: RunningTaskInfo, + ): RunningTaskInfo? { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d", + newFrontTaskInfo.taskId) + val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront( + taskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId), + newFrontTaskInfo.taskId) + val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack) + if (taskToMinimize != null) { + wct.reorder(taskToMinimize.token, false /* onTop */) + return taskToMinimize + } + return null + } + + /** + * Add a pending minimize transition change, to update the list of minimized apps once the + * transition goes through. + */ + fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) { + minimizeTransitionObserver.addPendingTransitionToken( + transition, TaskDetails(displayId, taskId)) + } + + /** + * Returns the maximum number of tasks that should ever be displayed at the same time in Desktop + * Mode. + */ + fun getMaxTaskLimit(): Int = DesktopModeStatus.getMaxTaskLimit() + + /** + * Returns the Task to minimize given 1. a list of visible tasks ordered from front to back and + * 2. a new task placed in front of all the others. + */ + fun getTaskToMinimizeIfNeeded( + visibleFreeformTaskIdsOrderedFrontToBack: List<Int>, + newTaskIdInFront: Int + ): RunningTaskInfo? { + return getTaskToMinimizeIfNeeded( + createOrderedTaskListWithGivenTaskInFront( + visibleFreeformTaskIdsOrderedFrontToBack, newTaskIdInFront)) + } + + /** Returns the Task to minimize given a list of visible tasks ordered from front to back. */ + fun getTaskToMinimizeIfNeeded( + visibleFreeformTaskIdsOrderedFrontToBack: List<Int> + ): RunningTaskInfo? { + if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: no need to minimize; tasks below limit") + // No need to minimize anything + return null + } + val taskToMinimize = + shellTaskOrganizer.getRunningTaskInfo( + visibleFreeformTaskIdsOrderedFrontToBack.last()) + if (taskToMinimize == null) { + KtProtoLog.e( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: taskToMinimize == null") + return null + } + return taskToMinimize + } + + private fun createOrderedTaskListWithGivenTaskInFront( + existingTaskIdsOrderedFrontToBack: List<Int>, + newTaskId: Int + ): List<Int> { + return listOf(newTaskId) + + existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId } + } + + @VisibleForTesting + fun getTransitionObserver(): TransitionObserver { + return minimizeTransitionObserver + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 0061d03af8e9..e341f2d4d4b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -150,20 +150,20 @@ class DragToDesktopTransitionHandler( * windowing mode changes to the dragged task. This is called when the dragged task is released * inside the desktop drop zone. */ - fun finishDragToDesktopTransition(wct: WindowContainerTransaction) { + fun finishDragToDesktopTransition(wct: WindowContainerTransaction): IBinder? { if (!inProgress) { // Don't attempt to finish a drag to desktop transition since there is no transition in // progress which means that the drag to desktop transition was never successfully // started. - return + return null } if (requireTransitionState().startAborted) { // Don't attempt to complete the drag-to-desktop since the start transition didn't // succeed as expected. Just reset the state as if nothing happened. clearState() - return + return null } - transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) + return transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 79bb5408df82..74b8f831cdc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -78,10 +78,12 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition /** * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP * @param wct WindowContainerTransaction for transition + * @return the token representing the started transition */ - public void moveToDesktop(@NonNull WindowContainerTransaction wct) { + public IBinder moveToDesktop(@NonNull WindowContainerTransaction wct) { final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this); mPendingTransitionTokens.add(token); + return token; } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index f2bdcae31956..6fea2036dbd1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -95,6 +95,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (DesktopModeStatus.isEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); + repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); if (taskInfo.isVisible) { if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, @@ -116,6 +117,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (DesktopModeStatus.isEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.removeFreeformTask(taskInfo.taskId); + repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); if (repository.removeActiveTask(taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); @@ -162,6 +164,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); + repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS index ec09827fa4d1..afddfab99a2b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS @@ -1,3 +1,2 @@ # WM shell sub-module pip owner hwwang@google.com -mateuszc@google.com 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 4c477373c32c..eb845db409e3 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 @@ -743,11 +743,6 @@ public class PipAnimationController { .alpha(tx, leash, 1f) .round(tx, leash, shouldApplyCornerRadius()) .shadow(tx, leash, shouldApplyShadowRadius()); - // TODO(b/178632364): this is a work around for the black background when - // entering PiP in button navigation mode. - if (isInPipDirection(direction)) { - tx.setWindowCrop(leash, getStartValue()); - } tx.show(leash); tx.apply(); } 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 57cf99211b6e..e885262658f4 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 @@ -597,6 +597,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } + if (mPipTransitionState.isEnteringPip() + && !mPipTransitionState.getInSwipePipToHomeTransition()) { + // If we are still entering PiP with Shell playing enter animation, jump-cut to + // the end of the enter animation and reschedule exitPip to run after enter-PiP + // has finished its transition and allowed the client to draw in PiP mode. + mPipTransitionController.end(() -> { + exitPip(animationDurationMs, requestEnterSplit); + }); + return; + } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "exitPip: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState); final WindowContainerTransaction wct = new WindowContainerTransaction(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index d60f5a631044..fdde3ee01264 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -43,7 +43,6 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; -import android.animation.Animator; import android.annotation.IntDef; import android.app.ActivityManager; import android.app.TaskInfo; @@ -348,9 +347,16 @@ public class PipTransition extends PipTransitionController { @Override public void end() { - Animator animator = mPipAnimationController.getCurrentAnimator(); - if (animator != null && animator.isRunning()) { - animator.end(); + end(null); + } + + @Override + public void end(@Nullable Runnable onTransitionEnd) { + if (mPipAnimationController.isAnimating()) { + mPipAnimationController.getCurrentAnimator().end(); + } + if (onTransitionEnd != null) { + onTransitionEnd.run(); } } @@ -818,8 +824,13 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo) { startTransaction.apply(); - finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), - mPipDisplayLayoutState.getDisplayBounds()); + if (info.getChanges().isEmpty()) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "removePipImmediately is called with empty changes"); + } else { + finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), + mPipDisplayLayoutState.getDisplayBounds()); + } mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(null); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 32442f740a52..4f71a02528c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -305,6 +305,14 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void end() { } + /** + * End the currently-playing PiP animation. + * + * @param onTransitionEnd callback to run upon finishing the playing transition. + */ + public void end(@Nullable Runnable onTransitionEnd) { + } + /** Starts the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */ public void startHighPerfSession() {} 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 c1adfffce074..d8ac8e948a97 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 @@ -219,6 +219,7 @@ public class PipTouchHandler { mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), this::onAccessibilityShowMenu, this::updateMovementBounds, this::animateToUnStashedState, mainExecutor); + mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize); // TODO(b/181599115): This should really be initializes as part of the pip controller, but // until all PIP implementations derive from the controller, just initialize the touch handler diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS index 6dabb3bf6f9a..79d1793819f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS @@ -1,4 +1,3 @@ # WM shell sub-module pip owner hwwang@google.com -mateuszc@google.com gabiyev@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index cc8e3e0934e6..472003cb435f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -208,6 +208,7 @@ public class PipTouchHandler { new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mTouchState, this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor, mPipPerfHintController); + mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize); if (PipUtils.isPip2ExperimentEnabled()) { shellInit.addInitCallback(this::onInit, this); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index c625b69deac0..a7829c905c69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; import android.annotation.Nullable; @@ -54,6 +55,7 @@ import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowAnimationState; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -283,6 +285,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private IBinder mTransition = null; private boolean mKeyguardLocked = false; private boolean mWillFinishToHome = false; + private Transitions.TransitionHandler mTakeoverHandler = null; /** The animation is idle, waiting for the user to choose a task to switch to. */ private static final int STATE_NORMAL = 0; @@ -576,9 +579,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "Applying transaction=%d", t.getId()); t.apply(); - Bundle b = new Bundle(1 /*capacity*/); + + mTakeoverHandler = mTransitions.getHandlerForTakeover(mTransition, info); + + Bundle b = new Bundle(2 /*capacity*/); b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId)); + b.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, mTakeoverHandler != null); try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.start: calling onAnimationStart with %d apps", @@ -597,6 +604,63 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return true; } + @Override + public void handOffAnimation( + RemoteAnimationTarget[] targets, WindowAnimationState[] states) { + mExecutor.execute(() -> { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.handOffAnimation", mInstanceId); + + if (mTakeoverHandler == null) { + Slog.e(TAG, "Tried to hand off an animation without a valid takeover " + + "handler."); + return; + } + + if (targets.length != states.length) { + Slog.e(TAG, "Tried to hand off an animation, but the number of targets " + + "(" + targets.length + ") doesn't match the number of states " + + "(" + states.length + ")"); + return; + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.handOffAnimation: got %d states for %d " + + "changes", mInstanceId, states.length, mInfo.getChanges().size()); + WindowAnimationState[] updatedStates = + new WindowAnimationState[mInfo.getChanges().size()]; + + // Ensure that the ordering of animation states is the same as that of matching + // changes in mInfo. prefixOrderIndex is set up in reverse order to that of the + // changes, so that's what we use to get to the correct ordering. + for (int i = 0; i < targets.length; i++) { + RemoteAnimationTarget target = targets[i]; + updatedStates[updatedStates.length - target.prefixOrderIndex] = states[i]; + } + + Transitions.TransitionFinishCallback finishCB = mFinishCB; + // Reset the callback here, so any stray calls that aren't coming from the new + // handler are ignored. + mFinishCB = null; + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.handOffAnimation: calling " + + "takeOverAnimation with %d states", mInstanceId, + updatedStates.length); + mTakeoverHandler.takeOverAnimation( + mTransition, mInfo, new SurfaceControl.Transaction(), + wct -> { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.handOffAnimation: finish " + + "callback", mInstanceId); + // Set the callback once again so we can finish correctly. + mFinishCB = finishCB; + finishInner(true /* toHome */, false /* userLeave */, + null /* finishCb */); + }, updatedStates); + }); + } + /** * Updates this controller when a new transition is requested mid-recents transition. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index fadc9706af6a..4c68106af1fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1762,10 +1762,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); mSplitLayout.update(finishT, true /* resetImePosition */); - mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash, - getMainStageBounds()); - mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash, - getSideStageBounds()); + mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash); + mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash); setDividerVisibility(true, finishT); // Ensure divider surface are re-parented back into the hierarchy at the end of the // transition. See Transition#buildFinishTransaction for more detail. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index f33ab33dafcc..f41bca36bb70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -218,8 +218,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { // Inflates split decor view only when the root task is visible. if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { if (taskInfo.isVisible) { - mSplitDecorManager.inflate(mContext, mRootLeash, - taskInfo.configuration.windowConfiguration.getBounds()); + mSplitDecorManager.inflate(mContext, mRootLeash); } else { mSyncQueue.runInSync(t -> mSplitDecorManager.release(t)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index da3aa4adc42c..e552e6cdacf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -30,7 +30,6 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; @@ -54,7 +53,6 @@ import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; -import com.android.internal.R; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.common.ShellExecutor; @@ -206,7 +204,6 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { final SplashWindowRecord record = (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId); if (record != null) { - record.parseAppSystemBarColor(context); // Block until we get the background color. final SplashScreenView contentView = viewSupplier.get(); if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { @@ -427,8 +424,6 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { private boolean mSetSplashScreen; private SplashScreenView mSplashView; - private int mSystemBarAppearance; - private boolean mDrawsSystemBarBackgrounds; SplashWindowRecord(IBinder appToken, View decorView, @StartingWindowInfo.StartingWindowType int suggestType) { @@ -448,19 +443,6 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { mSetSplashScreen = true; } - void parseAppSystemBarColor(Context context) { - final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); - mDrawsSystemBarBackgrounds = a.getBoolean( - R.styleable.Window_windowDrawsSystemBarBackgrounds, false); - if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { - mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; - } - if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { - mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; - } - a.recycle(); - } - @Override public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { if (mRootView == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java index 56c0d0e67cab..c886cc999216 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java @@ -42,4 +42,7 @@ public class ShellSharedConstants { public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode"; // See IDragAndDrop.aidl public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop"; + // See IRecentsAnimationController.aidl + public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION = + "extra_shell_can_hand_off_animation"; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index 35a1fa0a92f6..a85188a9e04d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -30,7 +30,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; -import android.os.Looper; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -121,6 +120,11 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + if (mTaskViewTaskController.isUsingShellTransitions()) { + // No need for additional work as it is already taken care of during + // prepareOpenAnimation(). + return; + } onLocationChanged(); if (taskInfo.taskDescription != null) { final int bgColor = taskInfo.taskDescription.getBackgroundColor(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 196e04edbb10..11aa402aa283 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.taskview; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.WindowManager.TRANSIT_CHANGE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,6 +49,23 @@ import java.util.concurrent.Executor; * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls. * * The reverse communication is done via the {@link TaskViewBase} interface. + * + * <ul> + * <li>The entry point for an activity based task view is {@link + * TaskViewTaskController#startActivity(PendingIntent, Intent, ActivityOptions, Rect)}</li> + * + * <li>The entry point for an activity (represented by {@link ShortcutInfo}) based task view + * is {@link TaskViewTaskController#startShortcutActivity(ShortcutInfo, ActivityOptions, Rect)} + * </li> + * + * <li>The entry point for a root-task based task view is {@link + * TaskViewTaskController#startRootTask(ActivityManager.RunningTaskInfo, SurfaceControl, + * WindowContainerTransaction)}. + * This method is special as it doesn't create a root task and instead expects that the + * launch root task is already created and started. This method just attaches the taskInfo to + * the TaskView. + * </li> + * </ul> */ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { @@ -155,8 +173,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * <p>The owner of this container must be allowed to access the shortcut information, * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method. * - * @param shortcut the shortcut used to launch the activity. - * @param options options for the activity. + * @param shortcut the shortcut used to launch the activity. + * @param options options for the activity. * @param launchBounds the bounds (window size and position) that the activity should be * launched in, in pixels and in screen coordinates. */ @@ -183,10 +201,10 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * Launch a new activity. * * @param pendingIntent Intent used to launch an activity. - * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} - * @param options options for the activity. - * @param launchBounds the bounds (window size and position) that the activity should be - * launched in, in pixels and in screen coordinates. + * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} + * @param options options for the activity. + * @param launchBounds the bounds (window size and position) that the activity should be + * launched in, in pixels and in screen coordinates. */ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds) { @@ -208,6 +226,35 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } } + + /** + * Attaches the given root task {@code taskInfo} in the task view. + * + * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int, + * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is + * used as an entry point for an already-created root-task in the task view. + * + * @param taskInfo the task info of the root task. + * @param leash the {@link android.content.pm.ShortcutInfo.Surface} of the root task + * @param wct The Window container work that should happen as part of this set up. + */ + public void startRootTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, + @Nullable WindowContainerTransaction wct) { + if (wct == null) { + wct = new WindowContainerTransaction(); + } + // This method skips the regular flow where an activity task is launched as part of a new + // transition in taskview and then transition is intercepted using the launchcookie. + // The task here is already created and running, it just needs to be reparented, resized + // and tracked correctly inside taskview. Which is done by calling + // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container + // transaction. + prepareOpenAnimationInternal(true /* newTask */, mTransaction /* startTransaction */, + null /* finishTransaction */, taskInfo, leash, wct); + mTransaction.apply(); + mTaskViewTransitions.startInstantTransition(TRANSIT_CHANGE, wct); + } + private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) { final Binder launchCookie = new Binder(); mShellExecutor.execute(() -> { @@ -342,7 +389,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { final SurfaceControl taskLeash = mTaskLeash; handleAndNotifyTaskRemoval(mTaskInfo); - // Unparent the task when this surface is destroyed mTransaction.reparent(taskLeash, null).apply(); resetTaskInfo(); } @@ -597,6 +643,15 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { @NonNull SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct) { + prepareOpenAnimationInternal(newTask, startTransaction, finishTransaction, taskInfo, leash, + wct); + } + + private void prepareOpenAnimationInternal(final boolean newTask, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, + ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, + WindowContainerTransaction wct) { mPendingInfo = null; mTaskInfo = taskInfo; mTaskToken = mTaskInfo.token; @@ -608,10 +663,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { // Also reparent on finishTransaction since the finishTransaction will reparent back // to its "original" parent by default. Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen(); - finishTransaction.reparent(mTaskLeash, mSurfaceControl) - .setPosition(mTaskLeash, 0, 0) - // TODO: maybe once b/280900002 is fixed this will be unnecessary - .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height()); + if (finishTransaction != null) { + finishTransaction.reparent(mTaskLeash, mSurfaceControl) + .setPosition(mTaskLeash, 0, 0) + // TODO: maybe once b/280900002 is fixed this will be unnecessary + .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height()); + } mTaskViewTransitions.updateBoundsState(this, boundsOnScreen); mTaskViewTransitions.updateVisibilityState(this, true /* visible */); wct.setBounds(mTaskToken, boundsOnScreen); @@ -632,6 +689,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor); } + mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash); if (mListener != null) { final int taskId = mTaskInfo.taskId; final ComponentName baseActivity = mTaskInfo.baseActivity; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 198ec82b5f21..e6d1b4593a46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -53,7 +53,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { new ArrayMap<>(); private final ArrayList<PendingTransition> mPending = new ArrayList<>(); private final Transitions mTransitions; - private final boolean[] mRegistered = new boolean[]{ false }; + private final boolean[] mRegistered = new boolean[]{false}; /** * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be @@ -122,6 +122,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { /** * Looks through the pending transitions for a closing transaction that matches the provided * `taskView`. + * * @param taskView the pending transition should be for this. */ private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) { @@ -135,8 +136,17 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } /** + * Starts a transition outside of the handler associated with {@link TaskViewTransitions}. + */ + public void startInstantTransition(@WindowManager.TransitionType int type, + WindowContainerTransaction wct) { + mTransitions.startTransition(type, wct, null); + } + + /** * Looks through the pending transitions for a opening transaction that matches the provided * `taskView`. + * * @param taskView the pending transition should be for this. */ @VisibleForTesting @@ -152,8 +162,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { /** * Looks through the pending transitions for one matching `taskView`. + * * @param taskView the pending transition should be for this. - * @param type the type of transition it's looking for + * @param type the type of transition it's looking for */ PendingTransition findPending(TaskViewTaskController taskView, int type) { for (int i = mPending.size() - 1; i >= 0; --i) { @@ -220,7 +231,24 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { startNextTransition(); } - void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { + /** Starts a new transition to make the given {@code taskView} visible. */ + public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { + setTaskViewVisible(taskView, visible, false /* reorder */); + } + + /** + * Starts a new transition to make the given {@code taskView} visible and optionally change + * the task order. + * + * @param taskView the task view which the visibility is being changed for + * @param visible the new visibility of the task view + * @param reorder whether to reorder the task or not. If this is {@code true}, the task will be + * reordered as per the given {@code visible}. For {@code visible = true}, task + * will be reordered to top. For {@code visible = false}, task will be reordered + * to the bottom + */ + public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible, + boolean reorder) { if (mTaskViews.get(taskView) == null) return; if (mTaskViews.get(taskView).mVisible == visible) return; if (taskView.getTaskInfo() == null) { @@ -231,6 +259,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */); wct.setBounds(taskView.getTaskInfo().token, mTaskViews.get(taskView).mBounds); + if (reorder) { + wct.reorder(taskView.getTaskInfo().token, visible /* onTop */); + } PendingTransition pending = new PendingTransition( visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */); mPending.add(pending); @@ -238,6 +269,22 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { // visibility is reported in transition. } + /** Starts a new transition to reorder the given {@code taskView}'s task. */ + public void reorderTaskViewTask(TaskViewTaskController taskView, boolean onTop) { + if (mTaskViews.get(taskView) == null) return; + if (taskView.getTaskInfo() == null) { + // Nothing to update, task is not yet available + return; + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(taskView.getTaskInfo().token, onTop /* onTop */); + PendingTransition pending = new PendingTransition( + onTop ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */); + mPending.add(pending); + startNextTransition(); + // visibility is reported in transition. + } + void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) { TaskViewRequestedState state = mTaskViews.get(taskView); if (state == null) return; @@ -380,7 +427,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()); finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()) - .setPosition(chg.getLeash(), 0, 0); + .setPosition(chg.getLeash(), 0, 0); changesHandled++; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 8746b8c8d55c..4bc0dc00175b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -61,9 +61,10 @@ import java.util.function.Consumer; /** * A handler for dealing with transitions involving multiple other handlers. For example: an - * activity in split-screen going into PiP. + * activity in split-screen going into PiP. Note this is provided as a handset-specific + * implementation of {@code MixedTransitionHandler}. */ -public class DefaultMixedHandler implements Transitions.TransitionHandler, +public class DefaultMixedHandler implements MixedTransitionHandler, RecentsTransitionHandler.RecentsMixedHandler { private final Transitions mPlayer; @@ -116,7 +117,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, final IBinder mTransition; protected final Transitions mPlayer; - protected final DefaultMixedHandler mMixedHandler; + protected final MixedTransitionHandler mMixedHandler; protected final PipTransitionController mPipHandler; protected final StageCoordinator mSplitHandler; protected final KeyguardTransitionHandler mKeyguardHandler; @@ -142,7 +143,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, int mInFlightSubAnimations = 0; MixedTransition(int type, IBinder transition, Transitions player, - DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) { mType = type; mTransition = transition; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index e9cd73b0df5e..b028bd65b438 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -41,7 +41,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { private final ActivityEmbeddingController mActivityEmbeddingController; DefaultMixedTransition(int type, IBinder transition, Transitions player, - DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, UnfoldTransitionHandler unfoldHandler, ActivityEmbeddingController activityEmbeddingController) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 9adb67c8a65e..2d6ba6ee7217 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -594,7 +594,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { .setName("animation-background") .setCallsite("DefaultTransitionHandler") .setColorLayer(); - final SurfaceControl backgroundSurface = colorLayerBuilder.build(); // Attaching the background surface to the transition root could unexpectedly make it // cover one of the split root tasks. To avoid this, put the background surface just @@ -605,8 +604,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (isSplitTaskInvolved) { mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder); } else { - startTransaction.reparent(backgroundSurface, info.getRootLeash()); + colorLayerBuilder.setParent(info.getRootLeash()); } + + final SurfaceControl backgroundSurface = colorLayerBuilder.build(); startTransaction.setColor(backgroundSurface, colorArray) .setLayer(backgroundSurface, -1) .show(backgroundSurface); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHandler.java new file mode 100644 index 000000000000..ff429fb12c94 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +/** + * Interface for a {@link Transitions.TransitionHandler} that can take the subset of transitions + * that it handles and further decompose those transitions into sub-transitions which can be + * independently delegated to separate handlers. + */ +public interface MixedTransitionHandler extends Transitions.TransitionHandler { + + // TODO(b/335685449) this currently exists purely as a marker interface for use in form-factor + // specific/sysui dagger modules. Going forward, we should define this in a meaningful + // way so as to provide a clear basis for expectations/behaviours associated with mixed + // transitions and their default handlers. + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index 0974cd13f249..ffc0b76b131d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -44,7 +44,7 @@ public class MixedTransitionHelper { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler, + @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler, @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + "entering PIP while Split-Screen is foreground."); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 94519a0d118c..69c41675e989 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -27,6 +27,7 @@ import android.window.IRemoteTransitionFinishedCallback; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowAnimationState; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; @@ -65,30 +66,9 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" + " transition %s for (#%d).", mRemote, info.getDebugId()); - final IBinder.DeathRecipient remoteDied = () -> { - Log.e(Transitions.TAG, "Remote transition died, finishing"); - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(null /* wct */)); - }; - IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { - @Override - public void onTransitionFinished(WindowContainerTransaction wct, - SurfaceControl.Transaction sct) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "Finished one-shot remote transition %s for (#%d).", mRemote, - info.getDebugId()); - if (mRemote.asBinder() != null) { - mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); - } - if (sct != null) { - finishTransaction.merge(sct); - } - mMainExecutor.execute(() -> { - finishCallback.onTransitionFinished(wct); - mRemote = null; - }); - } - }; + final IBinder.DeathRecipient remoteDied = createDeathRecipient(finishCallback); + IRemoteTransitionFinishedCallback cb = + createFinishedCallback(info, finishTransaction, finishCallback, remoteDied); Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread()); try { if (mRemote.asBinder() != null) { @@ -152,6 +132,51 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } @Override + public boolean takeOverAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction transaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull WindowAnimationState[] states) { + if (mTransition != transition) return false; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "Using registered One-shot " + + "remote transition %s to take over (#%d).", mRemote, info.getDebugId()); + + final IBinder.DeathRecipient remoteDied = createDeathRecipient(finishCallback); + IRemoteTransitionFinishedCallback cb = createFinishedCallback( + info, null /* finishTransaction */, finishCallback, remoteDied); + + Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread()); + + try { + if (mRemote.asBinder() != null) { + mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); + } + + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteStartT = + RemoteTransitionHandler.copyIfLocal(transaction, mRemote.getRemoteTransition()); + final TransitionInfo remoteInfo = + remoteStartT == transaction ? info : info.localRemoteCopy(); + mRemote.getRemoteTransition().takeOverAnimation( + transition, remoteInfo, remoteStartT, cb, states); + + // Assume that remote will apply the transaction. + transaction.clear(); + return true; + } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error running remote transition takeover.", e); + if (mRemote.asBinder() != null) { + mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } + finishCallback.onTransitionFinished(null /* wct */); + mRemote = null; + } + + return false; + } + + @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @Nullable TransitionRequestInfo request) { @@ -174,6 +199,41 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } } + private IBinder.DeathRecipient createDeathRecipient( + Transitions.TransitionFinishCallback finishCallback) { + return () -> { + Log.e(Transitions.TAG, "Remote transition died, finishing"); + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(null /* wct */)); + }; + } + + private IRemoteTransitionFinishedCallback createFinishedCallback( + @NonNull TransitionInfo info, + @Nullable SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull IBinder.DeathRecipient remoteDied) { + return new IRemoteTransitionFinishedCallback.Stub() { + @Override + public void onTransitionFinished(WindowContainerTransaction wct, + SurfaceControl.Transaction sct) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Finished one-shot remote transition %s for (#%d).", mRemote, + info.getDebugId()); + if (mRemote.asBinder() != null) { + mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } + if (finishTransaction != null && sct != null) { + finishTransaction.merge(sct); + } + mMainExecutor.execute(() -> { + finishCallback.onTransitionFinished(wct); + mRemote = null; + }); + } + }; + } + @Override public String toString() { return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 5b402a5a7d53..d6e64cfaf4d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -43,7 +43,7 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { private final DesktopTasksController mDesktopTasksController; RecentsMixedTransition(int type, IBinder transition, Transitions player, - DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, RecentsTransitionHandler recentsHandler, DesktopTasksController desktopTasksController) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index 4c4c5806ea55..d6860464d055 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.transition; +import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary; + import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; @@ -32,6 +34,7 @@ import android.window.RemoteTransition; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowAnimationState; import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; @@ -41,7 +44,9 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; /** * Handler that deals with RemoteTransitions. It will only request to handle a transition @@ -58,6 +63,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { /** Ordered by specificity. Last filters will be checked first */ private final ArrayList<Pair<TransitionFilter, RemoteTransition>> mFilters = new ArrayList<>(); + private final ArrayList<Pair<TransitionFilter, RemoteTransition>> mTakeoverFilters = + new ArrayList<>(); private final ArrayMap<IBinder, RemoteDeathHandler> mDeathHandlers = new ArrayMap<>(); @@ -70,14 +77,23 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { mFilters.add(new Pair<>(filter, remote)); } + void addFilteredForTakeover(TransitionFilter filter, RemoteTransition remote) { + handleDeath(remote.asBinder(), null /* finishCallback */); + mTakeoverFilters.add(new Pair<>(filter, remote)); + } + void removeFiltered(RemoteTransition remote) { boolean removed = false; - for (int i = mFilters.size() - 1; i >= 0; --i) { - if (mFilters.get(i).second.asBinder().equals(remote.asBinder())) { - mFilters.remove(i); - removed = true; + for (ArrayList<Pair<TransitionFilter, RemoteTransition>> filters + : Arrays.asList(mFilters, mTakeoverFilters)) { + for (int i = filters.size() - 1; i >= 0; --i) { + if (filters.get(i).second.asBinder().equals(remote.asBinder())) { + filters.remove(i); + removed = true; + } } } + if (removed) { unhandleDeath(remote.asBinder(), null /* finishCallback */); } @@ -237,6 +253,47 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } + @Nullable + @Override + public Transitions.TransitionHandler getHandlerForTakeover( + @NonNull IBinder transition, @NonNull TransitionInfo info) { + if (!returnAnimationFrameworkLibrary()) { + return null; + } + + for (Pair<TransitionFilter, RemoteTransition> registered : mTakeoverFilters) { + if (registered.first.matches(info)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "Found matching remote to takeover (#%d)", info.getDebugId()); + + OneShotRemoteHandler oneShot = + new OneShotRemoteHandler(mMainExecutor, registered.second); + oneShot.setTransition(transition); + return oneShot; + } + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "No matching remote found to takeover (#%d)", info.getDebugId()); + return null; + } + + @Override + public boolean takeOverAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction transaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull WindowAnimationState[] states) { + Transitions.TransitionHandler handler = getHandlerForTakeover(transition, info); + if (handler == null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "Take over request failed: no matching remote for (#%d)", info.getDebugId()); + return false; + } + ((OneShotRemoteHandler) handler).setTransition(transition); + return handler.takeOverAnimation(transition, info, transaction, finishCallback, states); + } + @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @@ -284,6 +341,34 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } + void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + + pw.println(prefix + "Registered Remotes:"); + if (mFilters.isEmpty()) { + pw.println(innerPrefix + "none"); + } else { + for (Pair<TransitionFilter, RemoteTransition> entry : mFilters) { + dumpRemote(pw, innerPrefix, entry.second); + } + } + + pw.println(prefix + "Registered Takeover Remotes:"); + if (mTakeoverFilters.isEmpty()) { + pw.println(innerPrefix + "none"); + } else { + for (Pair<TransitionFilter, RemoteTransition> entry : mTakeoverFilters) { + dumpRemote(pw, innerPrefix, entry.second); + } + } + } + + private void dumpRemote(@NonNull PrintWriter pw, String prefix, RemoteTransition remote) { + pw.print(prefix); + pw.print(remote.getDebugName()); + pw.println(" (" + Integer.toHexString(System.identityHashCode(remote)) + ")"); + } + /** NOTE: binder deaths can alter the filter order */ private class RemoteDeathHandler implements IBinder.DeathRecipient { private final IBinder mRemote; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 437a00e4a160..9b2922dff2f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -35,6 +35,7 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -43,9 +44,11 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SH import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityTaskManager; +import android.app.AppGlobals; import android.app.IApplicationThread; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.os.Handler; import android.os.IBinder; @@ -62,6 +65,7 @@ import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; +import android.window.WindowAnimationState; import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; @@ -124,8 +128,7 @@ public class Transitions implements RemoteCallable<Transitions>, static final String TAG = "ShellTransitions"; /** Set to {@code true} to enable shell transitions. */ - public static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); + public static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled(); public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); @@ -419,12 +422,24 @@ public class Transitions implements RemoteCallable<Transitions>, mHandlers.set(0, handler); } - /** Register a remote transition to be used when `filter` matches an incoming transition */ + /** + * Register a remote transition to be used for all operations except takeovers when `filter` + * matches an incoming transition. + */ public void registerRemote(@NonNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition) { mRemoteTransitionHandler.addFiltered(filter, remoteTransition); } + /** + * Register a remote transition to be used for all operations except takeovers when `filter` + * matches an incoming transition. + */ + public void registerRemoteForTakeover(@NonNull TransitionFilter filter, + @NonNull RemoteTransition remoteTransition) { + mRemoteTransitionHandler.addFilteredForTakeover(filter, remoteTransition); + } + /** Unregisters a remote transition and all associated filters */ public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { mRemoteTransitionHandler.removeFiltered(remoteTransition); @@ -1187,6 +1202,29 @@ public class Transitions implements RemoteCallable<Transitions>, } /** + * Checks whether a handler exists capable of taking over the given transition, and returns it. + * Otherwise it returns null. + */ + @Nullable + public TransitionHandler getHandlerForTakeover( + @NonNull IBinder transition, @NonNull TransitionInfo info) { + if (!returnAnimationFrameworkLibrary()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "Trying to get a handler for takeover but the flag is disabled"); + return null; + } + + for (TransitionHandler handler : mHandlers) { + TransitionHandler candidate = handler.getHandlerForTakeover(transition, info); + if (candidate != null) { + return candidate; + } + } + + return null; + } + + /** * Finish running animations (almost) immediately when a SLEEP transition comes in. We use this * as both a way to reduce unnecessary work (animations not visible while screen off) and as a * failsafe to unblock "stuck" animations (in particular remote animations). @@ -1328,6 +1366,49 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull TransitionFinishCallback finishCallback) { } /** + * Checks whether this handler is capable of taking over a transition matching `info`. + * {@link TransitionHandler#takeOverAnimation(IBinder, TransitionInfo, + * SurfaceControl.Transaction, TransitionFinishCallback, WindowAnimationState[])} is + * guaranteed to succeed if called on the handler returned by this method. + * + * Note that the handler returned by this method can either be itself, or a different one + * selected by this handler to take care of the transition on its behalf. + * + * @param transition The transition that should be taken over. + * @param info Information about the transition to be taken over. + * @return A handler capable of taking over a matching transition, or null. + */ + @Nullable + default TransitionHandler getHandlerForTakeover( + @NonNull IBinder transition, @NonNull TransitionInfo info) { + return null; + } + + /** + * Attempt to take over a running transition. This must succeed if this handler was returned + * by {@link TransitionHandler#getHandlerForTakeover(IBinder, TransitionInfo)}. + * + * @param transition The transition that should be taken over. + * @param info Information about the what is changing in the transition. + * @param transaction Contains surface changes that resulted from the transition. Any + * additional changes should be added to this transaction and committed + * inside this method. + * @param finishCallback Call this at the end of the animation, if the take-over succeeds. + * Note that this will be called instead of the callback originally + * passed to startAnimation(), so the caller should make sure all + * necessary cleanup happens here. This MUST be called on main thread. + * @param states The animation states of the transition's window at the time this method was + * called. + * @return true if the transition was taken over, false if not. + */ + default boolean takeOverAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction transaction, + @NonNull TransitionFinishCallback finishCallback, + @NonNull WindowAnimationState[] states) { + return false; + } + + /** * Potentially handles a startTransition request. * * @param transition The transition whose start is being requested. @@ -1432,16 +1513,21 @@ public class Transitions implements RemoteCallable<Transitions>, @Override public void registerRemote(@NonNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition) { - mMainExecutor.execute(() -> { - mRemoteTransitionHandler.addFiltered(filter, remoteTransition); - }); + mMainExecutor.execute( + () -> mRemoteTransitionHandler.addFiltered(filter, remoteTransition)); + } + + @Override + public void registerRemoteForTakeover(@NonNull TransitionFilter filter, + @NonNull RemoteTransition remoteTransition) { + mMainExecutor.execute(() -> mRemoteTransitionHandler.addFilteredForTakeover( + filter, remoteTransition)); } @Override public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { - mMainExecutor.execute(() -> { - mRemoteTransitionHandler.removeFiltered(remoteTransition); - }); + mMainExecutor.execute( + () -> mRemoteTransitionHandler.removeFiltered(remoteTransition)); } } @@ -1470,17 +1556,23 @@ public class Transitions implements RemoteCallable<Transitions>, public void registerRemote(@NonNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition) { executeRemoteCallWithTaskPermission(mTransitions, "registerRemote", - (transitions) -> { - transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition); - }); + (transitions) -> transitions.mRemoteTransitionHandler.addFiltered( + filter, remoteTransition)); + } + + @Override + public void registerRemoteForTakeover(@NonNull TransitionFilter filter, + @NonNull RemoteTransition remoteTransition) { + executeRemoteCallWithTaskPermission(mTransitions, "registerRemoteForTakeover", + (transitions) -> transitions.mRemoteTransitionHandler.addFilteredForTakeover( + filter, remoteTransition)); } @Override public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote", - (transitions) -> { - transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); - }); + (transitions) -> + transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition)); } @Override @@ -1565,6 +1657,8 @@ public class Transitions implements RemoteCallable<Transitions>, pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")"); } + mRemoteTransitionHandler.dump(pw, prefix); + pw.println(prefix + "Observers:"); for (TransitionObserver observer : mObservers) { pw.print(innerPrefix); @@ -1617,4 +1711,16 @@ public class Transitions implements RemoteCallable<Transitions>, } } } + + private static boolean getShellTransitEnabled() { + try { + if (AppGlobals.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE, 0)) { + return SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); + } + } catch (RemoteException re) { + Log.w(TAG, "Error getting system features"); + } + return true; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java index fa331af267fa..1897560deed7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java @@ -16,7 +16,12 @@ package com.android.wm.shell.transition.tracing; -import android.internal.perfetto.protos.PerfettoTrace; +import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT; + +import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellHandlerMapping; +import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellHandlerMappings; +import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellTransition; +import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket; import android.os.SystemClock; import android.os.Trace; import android.tracing.perfetto.DataSourceParams; @@ -44,7 +49,8 @@ public class PerfettoTransitionTracer implements TransitionTracer { public PerfettoTransitionTracer() { Producer.init(InitArguments.DEFAULTS); - mDataSource.register(DataSourceParams.DEFAULTS); + mDataSource.register( + new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); } /** @@ -72,11 +78,11 @@ public class PerfettoTransitionTracer implements TransitionTracer { final int handlerId = getHandlerId(handler); final ProtoOutputStream os = ctx.newTracePacket(); - final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); - os.write(PerfettoTrace.ShellTransition.ID, transitionId); - os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS, + final long token = os.start(TracePacket.SHELL_TRANSITION); + os.write(ShellTransition.ID, transitionId); + os.write(ShellTransition.DISPATCH_TIME_NS, SystemClock.elapsedRealtimeNanos()); - os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId); + os.write(ShellTransition.HANDLER, handlerId); os.end(token); }); } @@ -117,11 +123,11 @@ public class PerfettoTransitionTracer implements TransitionTracer { private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) { mDataSource.trace(ctx -> { final ProtoOutputStream os = ctx.newTracePacket(); - final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); - os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId); - os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS, + final long token = os.start(TracePacket.SHELL_TRANSITION); + os.write(ShellTransition.ID, mergeRequestedTransitionId); + os.write(ShellTransition.MERGE_REQUEST_TIME_NS, SystemClock.elapsedRealtimeNanos()); - os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId); + os.write(ShellTransition.MERGE_TARGET, playingTransitionId); os.end(token); }); } @@ -149,11 +155,11 @@ public class PerfettoTransitionTracer implements TransitionTracer { private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) { mDataSource.trace(ctx -> { final ProtoOutputStream os = ctx.newTracePacket(); - final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); - os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId); - os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS, + final long token = os.start(TracePacket.SHELL_TRANSITION); + os.write(ShellTransition.ID, mergeRequestedTransitionId); + os.write(ShellTransition.MERGE_TIME_NS, SystemClock.elapsedRealtimeNanos()); - os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId); + os.write(ShellTransition.MERGE_TARGET, playingTransitionId); os.end(token); }); } @@ -180,9 +186,9 @@ public class PerfettoTransitionTracer implements TransitionTracer { private void doLogAborted(int transitionId) { mDataSource.trace(ctx -> { final ProtoOutputStream os = ctx.newTracePacket(); - final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); - os.write(PerfettoTrace.ShellTransition.ID, transitionId); - os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS, + final long token = os.start(TracePacket.SHELL_TRANSITION); + os.write(ShellTransition.ID, transitionId); + os.write(ShellTransition.SHELL_ABORT_TIME_NS, SystemClock.elapsedRealtimeNanos()); os.end(token); }); @@ -196,14 +202,14 @@ public class PerfettoTransitionTracer implements TransitionTracer { mDataSource.trace(ctx -> { final ProtoOutputStream os = ctx.newTracePacket(); - final long mappingsToken = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS); + final long mappingsToken = os.start(TracePacket.SHELL_HANDLER_MAPPINGS); for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) { final String handler = entry.getKey(); final int handlerId = entry.getValue(); - final long mappingEntryToken = os.start(PerfettoTrace.ShellHandlerMappings.MAPPING); - os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId); - os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler); + final long mappingEntryToken = os.start(ShellHandlerMappings.MAPPING); + os.write(ShellHandlerMapping.ID, handlerId); + os.write(ShellHandlerMapping.NAME, handler); os.end(mappingEntryToken); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 43fd32ba1750..6671391efdeb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -200,7 +200,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition; - mRelayoutParams.mAllowCaptionInputFallthrough = false; relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 777ab9c17218..922c55f49a33 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -1037,7 +1037,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue, mRootTaskDisplayAreaOrganizer); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - windowDecoration.createResizeVeil(); final DragPositioningCallback dragPositioningCallback; if (!DesktopModeStatus.isVeiledResizeEnabled()) { @@ -1182,7 +1181,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final boolean inImmersiveMode = !source.isVisible(); // Calls WindowDecoration#relayout if decoration visibility needs to be updated if (inImmersiveMode != mInImmersiveMode) { - decor.relayout(decor.mTaskInfo); + if (Flags.enableDesktopWindowingImmersiveHandleHiding()) { + decor.relayout(decor.mTaskInfo); + } mInImmersiveMode = inImmersiveMode; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index da1699cd6e33..cb6495c6af38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -44,6 +44,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.Trace; import android.util.Log; import android.util.Size; import android.view.Choreographer; @@ -51,6 +52,7 @@ import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowManager; import android.widget.ImageButton; import android.window.WindowContainerTransaction; @@ -107,8 +109,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private MaximizeMenu mMaximizeMenu; private ResizeVeil mResizeVeil; - - private Drawable mAppIconDrawable; private Bitmap mAppIconBitmap; private CharSequence mAppName; @@ -160,7 +160,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + Trace.beginSection("DesktopModeWindowDecoration#loadAppInfo"); loadAppInfo(); + Trace.endSection(); } void setCaptionListeners( @@ -206,6 +208,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { + Trace.beginSection("DesktopModeWindowDecoration#relayout"); if (isHandleMenuActive()) { mHandleMenu.relayout(startT); } @@ -217,16 +220,22 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); + Trace.beginSection("DesktopModeWindowDecoration#relayout-inner"); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); + Trace.endSection(); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo + Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT"); mTaskOrganizer.applyTransaction(wct); + Trace.endSection(); if (mResult.mRootView == null) { // This means something blocks the window decor from showing, e.g. the task is hidden. // Nothing is set up in this case including the decoration surface. + Trace.endSection(); // DesktopModeWindowDecoration#relayout return; } + if (oldRootView != mResult.mRootView) { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) { mWindowDecorViewHolder = new DesktopModeFocusedWindowDecorationViewHolder( @@ -254,7 +263,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin throw new IllegalArgumentException("Unexpected layout resource id"); } } + Trace.beginSection("DesktopModeWindowDecoration#relayout-binding"); mWindowDecorViewHolder.bindData(mTaskInfo); + Trace.endSection(); if (!mTaskInfo.isFocused) { closeHandleMenu(); @@ -270,11 +281,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin updateExclusionRegion(); } closeDragResizeListener(); + Trace.endSection(); // DesktopModeWindowDecoration#relayout return; } if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) { closeDragResizeListener(); + Trace.beginSection("DesktopModeWindowDecoration#relayout-DragResizeInputListener"); mDragResizeListener = new DragResizeInputListener( mContext, mHandler, @@ -285,6 +298,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mSurfaceControlBuilderSupplier, mSurfaceControlTransactionSupplier, mDisplayController); + Trace.endSection(); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) @@ -309,6 +323,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT); } } + Trace.endSection(); // DesktopModeWindowDecoration#relayout } @VisibleForTesting @@ -326,11 +341,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) { - // If the app is requesting to customize the caption bar, allow input to fall through - // to the windows below so that the app can respond to input events on their custom - // content. - relayoutParams.mAllowCaptionInputFallthrough = - TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo); + if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { + // If the app is requesting to customize the caption bar, allow input to fall + // through to the windows below so that the app can respond to input events on + // their custom content. + relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; + } // Report occluding elements as bounding rects to the insets system so that apps can // draw in the empty space in the center: // First, the "app chip" section of the caption bar (+ some extra margins). @@ -345,6 +361,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end; controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); + } else if (captionLayoutId == R.layout.desktop_mode_focused_window_decor) { + // The focused decor (fullscreen/split) does not need to handle input because input in + // the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel. + relayoutParams.mInputFeatures + |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; } if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) { relayoutParams.mShadowRadiusId = taskInfo.isFocused @@ -444,12 +465,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } PackageManager pm = mContext.getApplicationContext().getPackageManager(); final IconProvider provider = new IconProvider(mContext); - mAppIconDrawable = provider.getIcon(activityInfo); + final Drawable appIconDrawable = provider.getIcon(activityInfo); final Resources resources = mContext.getResources(); final BaseIconFactory factory = new BaseIconFactory(mContext, resources.getDisplayMetrics().densityDpi, resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)); - mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT); + mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT); final ApplicationInfo applicationInfo = activityInfo.applicationInfo; mAppName = pm.getApplicationLabel(applicationInfo); } @@ -466,8 +487,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Create the resize veil for this task. Note the veil's visibility is View.GONE by default * until a resize event calls showResizeVeil below. */ - void createResizeVeil() { - mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconDrawable, mTaskInfo, + private void createResizeVeilIfNeeded() { + if (mResizeVeil != null) return; + mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo, mTaskSurface, mSurfaceControlTransactionSupplier); } @@ -475,6 +497,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Show the resize veil. */ public void showResizeVeil(Rect taskBounds) { + createResizeVeilIfNeeded(); mResizeVeil.showVeil(mTaskSurface, taskBounds); } @@ -482,6 +505,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Show the resize veil. */ public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) { + createResizeVeilIfNeeded(); mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 2c4092ac6d2c..93e2a21c6b02 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -24,11 +24,12 @@ import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.drawable.Drawable; +import android.os.Trace; import android.view.Display; import android.view.LayoutInflater; import android.view.SurfaceControl; @@ -64,7 +65,7 @@ public class ResizeVeil { private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory; private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final Drawable mAppIcon; + private final Bitmap mAppIcon; private ImageView mIconView; private int mIconSize; private SurfaceControl mParentSurface; @@ -97,7 +98,7 @@ public class ResizeVeil { public ResizeVeil(Context context, @NonNull DisplayController displayController, - Drawable appIcon, RunningTaskInfo taskInfo, + Bitmap appIcon, RunningTaskInfo taskInfo, SurfaceControl taskSurface, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { this(context, @@ -112,7 +113,7 @@ public class ResizeVeil { public ResizeVeil(Context context, @NonNull DisplayController displayController, - Drawable appIcon, RunningTaskInfo taskInfo, + Bitmap appIcon, RunningTaskInfo taskInfo, SurfaceControl taskSurface, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, SurfaceControlBuilderFactory surfaceControlBuilderFactory, @@ -135,6 +136,7 @@ public class ResizeVeil { // Display may not be available yet, skip this until then. return; } + Trace.beginSection("ResizeVeil#setupResizeVeil"); mVeilSurface = mSurfaceControlBuilderFactory .create("Resize veil of Task=" + mTaskInfo.taskId) .setContainerLayer() @@ -162,7 +164,7 @@ public class ResizeVeil { final View root = LayoutInflater.from(mContext) .inflate(R.layout.desktop_mode_resize_veil, null /* root */); mIconView = root.findViewById(R.id.veil_application_icon); - mIconView.setImageDrawable(mAppIcon); + mIconView.setImageBitmap(mAppIcon); final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( @@ -179,6 +181,7 @@ public class ResizeVeil { mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil"); mViewHost.setView(root, lp); + Trace.endSection(); } private boolean obtainDisplayOrRegisterListener() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 36da1ace8408..54656ffa1e93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -33,6 +33,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; +import android.os.Trace; import android.view.Display; import android.view.InsetsSource; import android.view.InsetsState; @@ -311,7 +312,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> boundingRects = null; } else { // The customizable region can at most be equal to the caption bar. - if (params.mAllowCaptionInputFallthrough) { + if (params.hasInputFeatureSpy()) { outResult.mCustomizableCaptionRegion.set(mCaptionInsetsRect); } boundingRects = new Rect[numOfElements]; @@ -324,7 +325,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> calculateBoundingRect(element, elementWidthPx, mCaptionInsetsRect); // Subtract the regions used by the caption elements, the rest is // customizable. - if (params.mAllowCaptionInputFallthrough) { + if (params.hasInputFeatureSpy()) { outResult.mCustomizableCaptionRegion.op(boundingRects[i], Region.Op.DIFFERENCE); } @@ -378,6 +379,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> startT.unsetColor(mTaskSurface); } + Trace.beginSection("CaptionViewHostLayout"); if (mCaptionWindowManager == null) { // Put caption under a container surface because ViewRootImpl sets the destination frame // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. @@ -394,11 +396,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); lp.setTitle("Caption of Task=" + mTaskInfo.taskId); lp.setTrustedOverlay(); - if (params.mAllowCaptionInputFallthrough) { - lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; - } else { - lp.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_SPY; - } + lp.inputFeatures = params.mInputFeatures; if (mViewHost == null) { mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, mCaptionWindowManager); @@ -412,6 +410,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } mViewHost.relayout(lp); } + Trace.endSection(); // CaptionViewHostLayout } private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element, @@ -605,7 +604,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mCaptionHeightId; int mCaptionWidthId; final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>(); - boolean mAllowCaptionInputFallthrough; + int mInputFeatures; int mShadowRadiusId; int mCornerRadius; @@ -620,7 +619,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionHeightId = Resources.ID_NULL; mCaptionWidthId = Resources.ID_NULL; mOccludingCaptionElements.clear(); - mAllowCaptionInputFallthrough = false; + mInputFeatures = 0; mShadowRadiusId = Resources.ID_NULL; mCornerRadius = 0; @@ -630,6 +629,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mWindowDecorConfig = null; } + boolean hasInputFeatureSpy() { + return (mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_SPY) != 0; + } + /** * Describes elements within the caption bar that could occlude app content, and should be * sent as bounding rectangles to the insets system. diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index d718e157afdb..0f24bb549158 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -12,3 +12,4 @@ jorgegil@google.com nmusgrave@google.com pbdr@google.com tkachenkoi@google.com +mpodolian@google.com diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS new file mode 100644 index 000000000000..73a5a23909c5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS @@ -0,0 +1,5 @@ +# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Freeform +# Bug component: 929241 + +uysalorhan@google.com +pragyabajoria@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt new file mode 100644 index 000000000000..5563bb9fa934 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitLandscape.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP +import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAllAppWithAppHeaderExitLandscape : CloseAllAppsWithAppHeaderExit(Rotation.ROTATION_90) { + @ExpectedScenarios(["CLOSE_APP", "CLOSE_LAST_APP"]) + @Test + override fun closeAllAppsInDesktop() = super.closeAllAppsInDesktop() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(CLOSE_APP) + .use(CLOSE_LAST_APP) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt new file mode 100644 index 000000000000..3d16d2219c78 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/CloseAllAppWithAppHeaderExitPortrait.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP +import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAllAppWithAppHeaderExitPortrait : CloseAllAppsWithAppHeaderExit(Rotation.ROTATION_0) { + @ExpectedScenarios(["CLOSE_APP", "CLOSE_LAST_APP"]) + @Test + override fun closeAllAppsInDesktop() = super.closeAllAppsInDesktop() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(CLOSE_APP) + .use(CLOSE_LAST_APP) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt new file mode 100644 index 000000000000..75dfeba3e662 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.flicker.AssertionInvocationGroup +import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd +import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways +import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart +import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd +import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd +import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart +import android.tools.flicker.assertors.assertions.LauncherWindowMovesToTop +import android.tools.flicker.config.AssertionTemplates +import android.tools.flicker.config.FlickerConfigEntry +import android.tools.flicker.config.ScenarioId +import android.tools.flicker.config.desktopmode.Components +import android.tools.flicker.extractors.ITransitionMatcher +import android.tools.flicker.extractors.ShellTransitionScenarioExtractor +import android.tools.traces.wm.Transition +import android.tools.traces.wm.TransitionType + +class DesktopModeFlickerScenarios { + companion object { + val END_DRAG_TO_DESKTOP = + FlickerConfigEntry( + scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"), + extractor = + ShellTransitionScenarioExtractor( + transitionMatcher = + object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + return transitions.filter { + it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP + } + } + } + ), + assertions = + AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP), + AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP), + AppWindowHasDesktopModeInitialBoundsAtTheEnd( + Components.DESKTOP_MODE_APP + ) + ) + .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + + val CLOSE_APP = + FlickerConfigEntry( + scenarioId = ScenarioId("CLOSE_APP"), + extractor = + ShellTransitionScenarioExtractor( + transitionMatcher = + object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + return transitions.filter { it.type == TransitionType.CLOSE } + } + } + ), + assertions = + AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP), + AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP), + AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP), + ) + .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + + val CLOSE_LAST_APP = + FlickerConfigEntry( + scenarioId = ScenarioId("CLOSE_LAST_APP"), + extractor = + ShellTransitionScenarioExtractor( + transitionMatcher = + object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + val lastTransition = + transitions.findLast { it.type == TransitionType.CLOSE } + return if (lastTransition != null) listOf(lastTransition) + else emptyList() + } + } + ), + assertions = + AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP), + AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP), + AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP), + LauncherWindowMovesToTop() + ) + .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt new file mode 100644 index 000000000000..9dfafe958b0b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP +import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterDesktopWithDragLandscape : EnterDesktopWithDrag(Rotation.ROTATION_90) { + @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) + @Test + override fun enterDesktopWithDrag() = super.enterDesktopWithDrag() + + companion object { + + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(END_DRAG_TO_DESKTOP) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt new file mode 100644 index 000000000000..1c7d6237eb8a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.END_DRAG_TO_DESKTOP +import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterDesktopWithDragPortrait : EnterDesktopWithDrag(Rotation.ROTATION_0) { + @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) + @Test + override fun enterDesktopWithDrag() = super.enterDesktopWithDrag() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(END_DRAG_TO_DESKTOP) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt new file mode 100644 index 000000000000..0c2b5015840d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.MailAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.flicker.service.common.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CloseAllAppsWithAppHeaderExit +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation)) + private val nonResizeableApp = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation)) + + + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode()) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + testApp.enterDesktopWithDrag(wmHelper, device) + mailApp.launchViaIntent(wmHelper) + nonResizeableApp.launchViaIntent(wmHelper) + } + + @Test + open fun closeAllAppsInDesktop() { + nonResizeableApp.closeDesktopApp(wmHelper, device) + mailApp.closeDesktopApp(wmHelper, device) + testApp.closeDesktopApp(wmHelper, device) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt new file mode 100644 index 000000000000..9e9998ef7c2a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.flicker.service.common.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + + +@Ignore("Base Test Class") +abstract class EnterDesktopWithDrag +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode()) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + } + + @Test + open fun enterDesktopWithDrag() { + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 9c1a88e1caa0..82c070cbf1c3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -16,10 +16,10 @@ package com.android.wm.shell; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -435,7 +435,8 @@ public class ShellTaskOrganizerTests extends ShellTestCase { public void testOnCameraCompatActivityChanged() { final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN); taskInfo1.displayId = DEFAULT_DISPLAY; - taskInfo1.appCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; + taskInfo1.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = + CAMERA_COMPAT_CONTROL_HIDDEN; final TrackingTaskListener taskListener = new TrackingTaskListener(); mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN); mOrganizer.onTaskAppeared(taskInfo1, null); @@ -449,7 +450,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { final RunningTaskInfo taskInfo2 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo2.displayId = taskInfo1.displayId; - taskInfo2.appCompatTaskInfo.cameraCompatControlState = + taskInfo2.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; taskInfo2.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo2); @@ -461,7 +462,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { final RunningTaskInfo taskInfo3 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo3.displayId = taskInfo1.displayId; - taskInfo3.appCompatTaskInfo.cameraCompatControlState = + taskInfo3.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; taskInfo3.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo3); @@ -474,7 +475,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo4.displayId = taskInfo1.displayId; taskInfo4.appCompatTaskInfo.topActivityInSizeCompat = true; - taskInfo4.appCompatTaskInfo.cameraCompatControlState = + taskInfo4.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; taskInfo4.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo4); @@ -485,7 +486,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { final RunningTaskInfo taskInfo5 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo5.displayId = taskInfo1.displayId; - taskInfo5.appCompatTaskInfo.cameraCompatControlState = + taskInfo5.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED; taskInfo5.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo5); @@ -496,7 +497,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { final RunningTaskInfo taskInfo6 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); taskInfo6.displayId = taskInfo1.displayId; - taskInfo6.appCompatTaskInfo.cameraCompatControlState = + taskInfo6.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; taskInfo6.isVisible = false; mOrganizer.onTaskInfoChanged(taskInfo6); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 65169e36a225..f99b4b2beef0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -354,6 +354,7 @@ public class BackAnimationControllerTest extends ShellTestCase { // Verify that we prevent any interaction with the animator callback in case a new gesture // starts while the current back animation has not ended, instead the gesture is queued triggerBackGesture(); + verify(mAnimatorCallback).setTriggerBack(eq(true)); verifyNoMoreInteractions(mAnimatorCallback); // Finish previous back navigation. @@ -394,6 +395,7 @@ public class BackAnimationControllerTest extends ShellTestCase { // starts while the current back animation has not ended, instead the gesture is queued triggerBackGesture(); releaseBackGesture(); + verify(mAnimatorCallback).setTriggerBack(eq(true)); verifyNoMoreInteractions(mAnimatorCallback); // Finish previous back navigation. @@ -532,7 +534,7 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test - public void callbackShouldDeliverProgress() throws RemoteException { + public void appCallback_receivesStartAndInvoke() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); final int type = BackNavigationInfo.TYPE_CALLBACK; @@ -551,8 +553,9 @@ public class BackAnimationControllerTest extends ShellTestCase { assertTrue("TriggerBack should have been true", result.mTriggerBack); verify(mAppCallback, times(1)).onBackStarted(any()); - verify(mAppCallback, times(1)).onBackProgressed(any()); verify(mAppCallback, times(1)).onBackInvoked(); + // Progress events should be generated from the app process. + verify(mAppCallback, never()).onBackProgressed(any()); verify(mAnimatorCallback, never()).onBackStarted(any()); verify(mAnimatorCallback, never()).onBackProgressed(any()); @@ -639,7 +642,7 @@ public class BackAnimationControllerTest extends ShellTestCase { */ private void doStartEvents(int startX, int moveX) { doMotionEvent(MotionEvent.ACTION_DOWN, startX); - mController.onPilferPointers(); + mController.onThresholdCrossed(); doMotionEvent(MotionEvent.ACTION_MOVE, moveX); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java index cebbbd890f05..158d640dca30 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java @@ -96,7 +96,7 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { .loadAnimation(any(), eq(true)); mCustomizeActivityAnimation.prepareNextAnimation( - new BackNavigationInfo.CustomAnimationInfo("TestPackage")); + new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0); final RemoteAnimationTarget close = createAnimationTarget(false); final RemoteAnimationTarget open = createAnimationTarget(true); // start animation with remote animation targets @@ -129,7 +129,7 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { .loadAnimation(any(), eq(true)); mCustomizeActivityAnimation.prepareNextAnimation( - new BackNavigationInfo.CustomAnimationInfo("TestPackage")); + new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0); final RemoteAnimationTarget close = createAnimationTarget(false); final RemoteAnimationTarget open = createAnimationTarget(true); // start animation with remote animation targets @@ -155,7 +155,7 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { @Test public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException { mCustomizeActivityAnimation.prepareNextAnimation( - new BackNavigationInfo.CustomAnimationInfo("TestPackage")); + new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0); // start animation without any remote animation targets final CountDownLatch finishCalled = new CountDownLatch(1); final Runnable finishCallback = finishCalled::countDown; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt deleted file mode 100644 index 6dbb1e2b8d92..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2022 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.back - -import android.util.MathUtils -import android.window.BackEvent -import org.junit.Assert.assertEquals -import org.junit.Test - -class TouchTrackerTest { - private fun linearTouchTracker(): TouchTracker = TouchTracker().apply { - setProgressThresholds(MAX_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR) - } - - private fun nonLinearTouchTracker(): TouchTracker = TouchTracker().apply { - setProgressThresholds(LINEAR_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR) - } - - private fun TouchTracker.assertProgress(expected: Float) { - val actualProgress = createProgressEvent().progress - assertEquals(expected, actualProgress, /* delta = */ 0f) - } - - @Test - fun generatesProgress_onStart() { - val linearTracker = linearTouchTracker() - linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) - val event = linearTracker.createStartEvent(null) - assertEquals(0f, event.progress, 0f) - } - - @Test - fun generatesProgress_leftEdge() { - val linearTracker = linearTouchTracker() - linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) - var touchX = 10f - val velocityX = 0f - val velocityY = 0f - - // Pre-commit - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) - - // Post-commit - touchX += 100f - linearTracker.setTriggerBack(true) - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) - - // Cancel - touchX -= 10f - linearTracker.setTriggerBack(false) - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress(0f) - - // Cancel more - touchX -= 10f - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress(0f) - - // Restarted, but pre-commit - val restartX = touchX - touchX += 10f - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE) - - // continue restart within pre-commit - touchX += 10f - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE) - - // Restarted, post-commit - touchX += 10f - linearTracker.setTriggerBack(true) - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) - } - - @Test - fun generatesProgress_rightEdge() { - val linearTracker = linearTouchTracker() - linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT) - var touchX = INITIAL_X_RIGHT_EDGE - 10 // Fake right edge - val velocityX = 0f - val velocityY = 0f - val target = MAX_DISTANCE - - // Pre-commit - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target) - - // Post-commit - touchX -= 100f - linearTracker.setTriggerBack(true) - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target) - - // Cancel - touchX += 10f - linearTracker.setTriggerBack(false) - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress(0f) - - // Cancel more - touchX += 10f - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress(0f) - - // Restarted, but pre-commit - val restartX = touchX - touchX -= 10f - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((restartX - touchX) / target) - - // continue restart within pre-commit - touchX -= 10f - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((restartX - touchX) / target) - - // Restarted, post-commit - touchX -= 10f - linearTracker.setTriggerBack(true) - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target) - } - - @Test - fun generatesNonLinearProgress_leftEdge() { - val nonLinearTracker = nonLinearTouchTracker() - nonLinearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) - var touchX = 10f - val velocityX = 0f - val velocityY = 0f - val linearTarget = LINEAR_DISTANCE + (MAX_DISTANCE - LINEAR_DISTANCE) * NON_LINEAR_FACTOR - - // Pre-commit: linear progress - nonLinearTracker.update(touchX, 0f, velocityX, velocityY) - nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget) - - // Post-commit: still linear progress - touchX += 100f - nonLinearTracker.setTriggerBack(true) - nonLinearTracker.update(touchX, 0f, velocityX, velocityY) - nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget) - - // still linear progress - touchX = INITIAL_X_LEFT_EDGE + LINEAR_DISTANCE - nonLinearTracker.update(touchX, 0f, velocityX, velocityY) - nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget) - - // non linear progress - touchX += 10 - nonLinearTracker.update(touchX, 0f, velocityX, velocityY) - val nonLinearTouch = (touchX - INITIAL_X_LEFT_EDGE) - LINEAR_DISTANCE - val nonLinearProgress = nonLinearTouch / NON_LINEAR_DISTANCE - val nonLinearTarget = MathUtils.lerp(linearTarget, MAX_DISTANCE, nonLinearProgress) - nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget) - } - - @Test - fun restartingGesture_resetsInitialTouchX_leftEdge() { - val linearTracker = linearTouchTracker() - linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) - var touchX = 100f - val velocityX = 0f - val velocityY = 0f - - // assert that progress is increased when increasing touchX - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) - - // assert that progress is reset to 0 when start location is updated - linearTracker.updateStartLocation() - linearTracker.assertProgress(0f) - - // assert that progress remains 0 when touchX is decreased - touchX -= 50 - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress(0f) - - // assert that progress uses new minimal touchX for progress calculation - val newInitialTouchX = touchX - touchX += 100 - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE) - - // assert the same for triggerBack==true - linearTracker.triggerBack = true - linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE) - } - - @Test - fun restartingGesture_resetsInitialTouchX_rightEdge() { - val linearTracker = linearTouchTracker() - linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT) - - var touchX = INITIAL_X_RIGHT_EDGE - 100f - val velocityX = 0f - val velocityY = 0f - - // assert that progress is increased when decreasing touchX - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / MAX_DISTANCE) - - // assert that progress is reset to 0 when start location is updated - linearTracker.updateStartLocation() - linearTracker.assertProgress(0f) - - // assert that progress remains 0 when touchX is increased - touchX += 50 - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress(0f) - - // assert that progress uses new maximal touchX for progress calculation - val newInitialTouchX = touchX - touchX -= 100 - linearTracker.update(touchX, 0f, velocityX, velocityY) - linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE) - - // assert the same for triggerBack==true - linearTracker.triggerBack = true - linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE) - } - - companion object { - private const val MAX_DISTANCE = 500f - private const val LINEAR_DISTANCE = 400f - private const val NON_LINEAR_DISTANCE = MAX_DISTANCE - LINEAR_DISTANCE - private const val NON_LINEAR_FACTOR = 0.2f - private const val INITIAL_X_LEFT_EDGE = 5f - private const val INITIAL_X_RIGHT_EDGE = MAX_DISTANCE - INITIAL_X_LEFT_EDGE - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index fef81af8946b..afae653f0682 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -16,8 +16,8 @@ package com.android.wm.shell.compatui; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -34,7 +34,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; -import android.app.AppCompatTaskInfo.CameraCompatControlState; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.Context; import android.content.res.Configuration; @@ -689,7 +689,8 @@ public class CompatUIControllerTest extends ShellTestCase { taskInfo.taskId = taskId; taskInfo.displayId = displayId; taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat; - taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = + cameraCompatControlState; taskInfo.isVisible = isVisible; taskInfo.isFocused = isFocused; taskInfo.isTopActivityTransparent = isTopActivityTransparent; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index dd358e757fde..cd3e8cb0e8e1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -16,10 +16,10 @@ package com.android.wm.shell.compatui; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -28,7 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import android.app.ActivityManager; -import android.app.AppCompatTaskInfo.CameraCompatControlState; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -222,7 +222,8 @@ public class CompatUILayoutTest extends ShellTestCase { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = TASK_ID; taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat; - taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = + cameraCompatControlState; taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000; taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000; taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 4f261cd79d39..5209d0e4bdd0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -16,10 +16,10 @@ package com.android.wm.shell.compatui; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.WindowInsets.Type.navigationBars; @@ -37,7 +37,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.app.ActivityManager; -import android.app.AppCompatTaskInfo; +import android.app.CameraCompatTaskInfo; import android.app.TaskInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -521,11 +521,12 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } private static TaskInfo createTaskInfo(boolean hasSizeCompat, - @AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) { + @CameraCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = TASK_ID; taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat; - taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = + cameraCompatControlState; taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; // Letterboxed activity that takes half the screen should show size compat restart button taskInfo.configuration.windowConfiguration.setBounds( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java index 38d6ea1839c4..02316125bcc3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -16,7 +16,7 @@ package com.android.wm.shell.compatui; -import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -25,7 +25,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import android.app.ActivityManager; -import android.app.AppCompatTaskInfo.CameraCompatControlState; +import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.ComponentName; import android.testing.AndroidTestingRunner; @@ -148,7 +148,8 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = TASK_ID; taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat; - taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = + cameraCompatControlState; taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); return taskInfo; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 81ba4b37d13b..94e168ed70ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -292,6 +292,24 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() { + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + + final Rect stableBounds = mWindowManager.getTaskStableBounds(); + + // Letterboxed activity that has user fullscreen override should always show button, + // layout should be inflated + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableBounds.height(); + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width(); + taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled = true; + + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + } + + @Test public void testUpdateDisplayLayout() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index b2b54acf4585..dca7be12fffc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -483,6 +483,102 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull() } + @Test + fun minimizeTaskNotCalled_noTasksMinimized() { + assertThat(repo.isMinimizedTask(taskId = 0)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() + } + + @Test + fun minimizeTask_onlyThatTaskIsMinimized() { + repo.minimizeTask(displayId = 0, taskId = 0) + + assertThat(repo.isMinimizedTask(taskId = 0)).isTrue() + assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() + } + + @Test + fun unminimizeTask_taskNoLongerMinimized() { + repo.minimizeTask(displayId = 0, taskId = 0) + repo.unminimizeTask(displayId = 0, taskId = 0) + + assertThat(repo.isMinimizedTask(taskId = 0)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() + } + + @Test + fun unminimizeTask_nonExistentTask_doesntCrash() { + repo.unminimizeTask(displayId = 0, taskId = 0) + + assertThat(repo.isMinimizedTask(taskId = 0)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() + } + + + @Test + fun updateVisibleFreeformTasks_toVisible_taskIsUnminimized() { + repo.minimizeTask(displayId = 10, taskId = 2) + + repo.updateVisibleFreeformTasks(displayId = 10, taskId = 2, visible = true) + + assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() + } + + @Test + fun isDesktopModeShowing_noActiveTasks_returnsFalse() { + assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse() + } + + @Test + fun isDesktopModeShowing_noTasksVisible_returnsFalse() { + repo.addActiveTask(displayId = 0, taskId = 1) + repo.addActiveTask(displayId = 0, taskId = 2) + + assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse() + } + + @Test + fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() { + repo.addActiveTask(displayId = 0, taskId = 1) + repo.addActiveTask(displayId = 0, taskId = 2) + repo.updateVisibleFreeformTasks(displayId = 0, taskId = 1, visible = true) + + assertThat(repo.isDesktopModeShowing(displayId = 0)).isTrue() + } + + @Test + fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() { + repo.addActiveTask(displayId = 0, taskId = 1) + repo.addActiveTask(displayId = 0, taskId = 2) + repo.addActiveTask(displayId = 0, taskId = 3) + // The front-most task will be the one added last through addOrMoveFreeformTaskToTop + repo.addOrMoveFreeformTaskToTop(taskId = 3) + repo.addOrMoveFreeformTaskToTop(taskId = 2) + repo.addOrMoveFreeformTaskToTop(taskId = 1) + + assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0)).isEqualTo( + listOf(1, 2, 3)) + } + + @Test + fun getActiveNonMinimizedTasksOrderedFrontToBack_minimizedTaskNotIncluded() { + repo.addActiveTask(displayId = 0, taskId = 1) + repo.addActiveTask(displayId = 0, taskId = 2) + repo.addActiveTask(displayId = 0, taskId = 3) + // The front-most task will be the one added last through addOrMoveFreeformTaskToTop + repo.addOrMoveFreeformTaskToTop(taskId = 3) + repo.addOrMoveFreeformTaskToTop(taskId = 2) + repo.addOrMoveFreeformTaskToTop(taskId = 1) + repo.minimizeTask(displayId = 0, taskId = 2) + + assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0)).isEqualTo( + listOf(1, 3)) + } + + class TestListener : DesktopModeTaskRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 64f604119a8b..ad4b720facd7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -105,6 +105,7 @@ import org.mockito.Mockito.verify import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.capture import org.mockito.quality.Strictness +import java.util.Optional import org.mockito.Mockito.`when` as whenever /** @@ -145,6 +146,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository + private lateinit var desktopTasksLimiter: DesktopTasksLimiter private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private val shellExecutor = TestShellExecutor() @@ -160,9 +162,12 @@ class DesktopTasksControllerTest : ShellTestCase() { shellInit = Mockito.spy(ShellInit(testExecutor)) desktopModeTaskRepository = DesktopModeTaskRepository() + desktopTasksLimiter = + DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } + whenever(enterDesktopTransitionHandler.moveToDesktop(any())).thenAnswer { Binder() } whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) @@ -203,7 +208,8 @@ class DesktopTasksControllerTest : ShellTestCase() { launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, - shellExecutor + shellExecutor, + Optional.of(desktopTasksLimiter), ) } @@ -409,6 +415,25 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun showDesktopApps_dontReorderMinimizedTask() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + val minimizedTask = setUpFreeformTask() + markTaskHidden(freeformTask) + markTaskHidden(minimizedTask) + desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = getLatestWct( + type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(2) + // Reorder home and freeform task to top, don't reorder the minimized task + wct.assertReorderAt(index = 0, homeTask, toTop = true) + wct.assertReorderAt(index = 1, freeformTask, toTop = true) + } + + @Test fun getVisibleTaskCount_noTasks_returnsZero() { assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) } @@ -606,6 +631,24 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_bringsTasksOverLimit_dontShowBackTask() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val homeTask = setUpHomeTask() + val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() } + val newTask = setUpFullscreenTask() + + controller.moveToDesktop(newTask) + + val wct = getLatestMoveToDesktopWct() + assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home + wct.assertReorderAt(0, homeTask) + for (i in 1..<taskLimit) { // Skipping freeformTasks[0] + wct.assertReorderAt(index = i, task = freeformTasks[i]) + } + wct.assertReorderAt(taskLimit, newTask) + } + + @Test fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! @@ -659,6 +702,20 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + setUpHomeTask() + val freeformTasks = (1..taskLimit + 1).map { _ -> setUpFreeformTask() } + + controller.moveTaskToFront(freeformTasks[0]) + + val wct = getLatestWct(type = TRANSIT_TO_FRONT) + assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize + wct.assertReorderAt(0, freeformTasks[0], toTop = true) + wct.assertReorderAt(1, freeformTasks[1], toTop = false) + } + + @Test fun moveToNextDisplay_noOtherDisplays() { whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -777,6 +834,38 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + // Make sure we only reorder the new task to top (we don't reorder the old task to bottom) + assertThat(wct?.hierarchyOps?.size).isEqualTo(1) + wct!!.assertReorderAt(0, fullscreenTask, toTop = true) + } + + @Test + fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() } + freeformTasks.forEach { markTaskVisible(it) } + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + // Make sure we reorder the new task to top, and the back task to the bottom + assertThat(wct!!.hierarchyOps.size).isEqualTo(2) + wct!!.assertReorderAt(0, fullscreenTask, toTop = true) + wct!!.assertReorderAt(1, freeformTasks[0], toTop = false) + } + + @Test fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -841,6 +930,22 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() } + freeformTasks.forEach { markTaskVisible(it) } + val newFreeformTask = createFreeformTask() + + val wct = + controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN)) + + assertThat(wct?.hierarchyOps?.size).isEqualTo(1) + wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom + } + + @Test fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1352,11 +1457,16 @@ private fun WindowContainerTransaction.assertIndexInBounds(index: Int) { .isGreaterThan(index) } -private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) { +private fun WindowContainerTransaction.assertReorderAt( + index: Int, + task: RunningTaskInfo, + toTop: Boolean? = null +) { assertIndexInBounds(index) val op = hierarchyOps[index] assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER) assertThat(op.container).isEqualTo(task.token.asBinder()) + toTop?.let { assertThat(op.toTop).isEqualTo(it) } } private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt new file mode 100644 index 000000000000..38ea03471b07 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.os.Binder +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_TO_BACK +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask +import com.android.wm.shell.transition.TransitionInfoBuilder +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.util.StubTransaction +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.quality.Strictness + + +/** + * Test class for {@link DesktopTasksLimiter} + * + * Usage: atest WMShellUnitTests:DesktopTasksLimiterTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopTasksLimiterTest : ShellTestCase() { + + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + + @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer + @Mock lateinit var transitions: Transitions + + private lateinit var mockitoSession: StaticMockitoSession + private lateinit var desktopTasksLimiter: DesktopTasksLimiter + private lateinit var desktopTaskRepo: DesktopModeTaskRepository + + @Before + fun setUp() { + mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java).startMocking() + `when`(DesktopModeStatus.isEnabled()).thenReturn(true) + + desktopTaskRepo = DesktopModeTaskRepository() + + desktopTasksLimiter = DesktopTasksLimiter( + transitions, desktopTaskRepo, shellTaskOrganizer) + } + + @After + fun tearDown() { + mockitoSession.finishMocking() + } + + // Currently, the task limit can be overridden through an adb flag. This test ensures the limit + // hasn't been overridden. + @Test + fun getMaxTaskLimit_isSameAsConstant() { + assertThat(desktopTasksLimiter.getMaxTaskLimit()).isEqualTo( + DesktopModeStatus.DEFAULT_MAX_TASK_LIMIT) + } + + @Test + fun addPendingMinimizeTransition_taskIsNotMinimized() { + val task = setUpFreeformTask() + markTaskHidden(task) + + desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() + } + + @Test + fun onTransitionReady_noPendingTransition_taskIsNotMinimized() { + val task = setUpFreeformTask() + markTaskHidden(task) + + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + Binder() /* transition */, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() + } + + @Test + fun onTransitionReady_differentPendingTransition_taskIsNotMinimized() { + val pendingTransition = Binder() + val taskTransition = Binder() + val task = setUpFreeformTask() + markTaskHidden(task) + desktopTasksLimiter.addPendingMinimizeChange( + pendingTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + taskTransition /* transition */, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() + } + + @Test + fun onTransitionReady_pendingTransition_noTaskChange_taskVisible_taskIsNotMinimized() { + val transition = Binder() + val task = setUpFreeformTask() + markTaskVisible(task) + desktopTasksLimiter.addPendingMinimizeChange( + transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).build(), + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() + } + + @Test + fun onTransitionReady_pendingTransition_noTaskChange_taskInvisible_taskIsMinimized() { + val transition = Binder() + val task = setUpFreeformTask() + markTaskHidden(task) + desktopTasksLimiter.addPendingMinimizeChange( + transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).build(), + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() + } + + @Test + fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() { + val transition = Binder() + val task = setUpFreeformTask() + desktopTasksLimiter.addPendingMinimizeChange( + transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() + } + + @Test + fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() { + val mergedTransition = Binder() + val newTransition = Binder() + val task = setUpFreeformTask() + desktopTasksLimiter.addPendingMinimizeChange( + mergedTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + desktopTasksLimiter.getTransitionObserver().onTransitionMerged( + mergedTransition, newTransition) + + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + newTransition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() + } + + @Test + fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + (1..<taskLimit).forEach { _ -> setUpFreeformTask() } + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded( + displayId = DEFAULT_DISPLAY, + wct = wct, + newFrontTaskInfo = setUpFreeformTask()) + + assertThat(minimizedTaskId).isNull() + assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added + } + + @Test + fun addAndGetMinimizeTaskChangesIfNeeded_tasksAboveLimit_backTaskMinimized() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + // The following list will be ordered bottom -> top, as the last task is moved to top last. + val tasks = (1..taskLimit).map { setUpFreeformTask() } + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded( + displayId = DEFAULT_DISPLAY, + wct = wct, + newFrontTaskInfo = setUpFreeformTask()) + + assertThat(minimizedTaskId).isEqualTo(tasks.first()) + assertThat(wct.hierarchyOps.size).isEqualTo(1) + assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REORDER) + assertThat(wct.hierarchyOps[0].toTop).isFalse() // Reorder to bottom + } + + @Test + fun addAndGetMinimizeTaskChangesIfNeeded_nonMinimizedTasksWithinLimit_noTaskMinimized() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val tasks = (1..taskLimit).map { setUpFreeformTask() } + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId) + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded( + displayId = 0, + wct = wct, + newFrontTaskInfo = setUpFreeformTask()) + + assertThat(minimizedTaskId).isNull() + assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added + } + + @Test + fun getTaskToMinimizeIfNeeded_tasksWithinLimit_returnsNull() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val tasks = (1..taskLimit).map { setUpFreeformTask() } + + val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded( + visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId }) + + assertThat(minimizedTask).isNull() + } + + @Test + fun getTaskToMinimizeIfNeeded_tasksAboveLimit_returnsBackTask() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val tasks = (1..taskLimit + 1).map { setUpFreeformTask() } + + val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded( + visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId }) + + // first == front, last == back + assertThat(minimizedTask).isEqualTo(tasks.last()) + } + + @Test + fun getTaskToMinimizeIfNeeded_withNewTask_tasksAboveLimit_returnsBackTask() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val tasks = (1..taskLimit).map { setUpFreeformTask() } + + val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded( + visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId }, + newTaskIdInFront = setUpFreeformTask().taskId) + + // first == front, last == back + assertThat(minimizedTask).isEqualTo(tasks.last()) + } + + private fun setUpFreeformTask( + displayId: Int = DEFAULT_DISPLAY, + ): RunningTaskInfo { + val task = createFreeformTask(displayId) + `when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + desktopTaskRepo.addActiveTask(displayId, task.taskId) + desktopTaskRepo.addOrMoveFreeformTaskToTop(task.taskId) + return task + } + + private fun markTaskVisible(task: RunningTaskInfo) { + desktopTaskRepo.updateVisibleFreeformTasks( + task.displayId, + task.taskId, + visible = true + ) + } + + private fun markTaskHidden(task: RunningTaskInfo) { + desktopTaskRepo.updateVisibleFreeformTasks( + task.displayId, + task.taskId, + visible = false + ) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index d7c46104b6b1..0434742c571b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -44,7 +44,6 @@ import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; -import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -498,6 +497,31 @@ public class TaskViewTest extends ShellTestCase { } @Test + public void testStartRootTask_setsBoundsAndVisibility() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + TaskViewBase taskViewBase = mock(TaskViewBase.class); + Rect bounds = new Rect(0, 0, 100, 100); + when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds); + mTaskViewTaskController.setTaskViewBase(taskViewBase); + + // Surface created, but task not available so bounds / visibility isn't set + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + verify(mTaskViewTransitions, never()).updateVisibilityState( + eq(mTaskViewTaskController), eq(true)); + + // Make the task available + WindowContainerTransaction wct = mock(WindowContainerTransaction.class); + mTaskViewTaskController.startRootTask(mTaskInfo, mLeash, wct); + + // Bounds got set + verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds)); + // Visibility & bounds state got set + verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(true)); + verify(mTaskViewTransitions).updateBoundsState(eq(mTaskViewTaskController), eq(bounds)); + } + + @Test public void testTaskViewPrepareOpenAnimationSetsBoundsAndVisibility() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java index fbc0db9c2850..d3e40f21db23 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java @@ -18,6 +18,7 @@ package com.android.wm.shell.taskview; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.google.common.truth.Truth.assertThat; @@ -208,6 +209,48 @@ public class TaskViewTransitionsTest extends ShellTestCase { } @Test + public void testReorderTask_movedToFrontTransaction() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + mTaskViewTransitions.reorderTaskViewTask(mTaskViewTaskController, true); + // Consume the pending transaction from order change + TaskViewTransitions.PendingTransition pending = + mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); + assertThat(pending).isNotNull(); + mTaskViewTransitions.startAnimation(pending.mClaimed, + mock(TransitionInfo.class), + new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), + mock(Transitions.TransitionFinishCallback.class)); + + // Verify it was consumed + TaskViewTransitions.PendingTransition pending2 = + mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT); + assertThat(pending2).isNull(); + } + + @Test + public void testReorderTask_movedToBackTransaction() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + mTaskViewTransitions.reorderTaskViewTask(mTaskViewTaskController, false); + // Consume the pending transaction from order change + TaskViewTransitions.PendingTransition pending = + mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_BACK); + assertThat(pending).isNotNull(); + mTaskViewTransitions.startAnimation(pending.mClaimed, + mock(TransitionInfo.class), + new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), + mock(Transitions.TransitionFinishCallback.class)); + + // Verify it was consumed + TaskViewTransitions.PendingTransition pending2 = + mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_BACK); + assertThat(pending2).isNull(); + } + + @Test public void test_startAnimation_setsTaskNotFound() { assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 2366917a0158..964d86e8bd35 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -73,6 +73,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import android.util.Pair; import android.view.IRecentsAnimationRunner; @@ -87,6 +88,7 @@ import android.window.RemoteTransitionStub; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowAnimationState; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -98,6 +100,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; +import com.android.systemui.shared.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; @@ -114,6 +117,7 @@ import com.android.wm.shell.sysui.ShellSharedConstants; import com.android.wm.shell.util.StubTransaction; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -141,6 +145,9 @@ public class ShellTransitionTests extends ShellTestCase { private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + @Rule + public final SetFlagsRule setFlagsRule = new SetFlagsRule(); + @Before public void setUp() { doAnswer(invocation -> new Binder()) @@ -475,11 +482,97 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testRegisteredRemoteTransitionTakeover() { + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IRemoteTransition testRemote = new RemoteTransitionStub() { + @Override + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + final Transitions.TransitionHandler takeoverHandler = + transitions.getHandlerForTakeover(token, info); + + if (takeoverHandler == null) { + finishCallback.onTransitionFinished(null /* wct */, null /* sct */); + return; + } + + takeoverHandler.takeOverAnimation(token, info, new SurfaceControl.Transaction(), + wct -> { + try { + finishCallback.onTransitionFinished(wct, null /* sct */); + } catch (RemoteException e) { + // Fail + } + }, new WindowAnimationState[info.getChanges().size()]); + } + }; + final boolean[] takeoverRemoteCalled = new boolean[]{false}; + IRemoteTransition testTakeoverRemote = new RemoteTransitionStub() { + @Override + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) {} + + @Override + public void takeOverAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction startTransaction, + IRemoteTransitionFinishedCallback finishCallback, WindowAnimationState[] states) + throws RemoteException { + takeoverRemoteCalled[0] = true; + finishCallback.onTransitionFinished(null /* wct */, null /* sct */); + } + }; + + TransitionFilter filter = new TransitionFilter(); + filter.mRequirements = + new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()}; + filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + + transitions.registerRemote(filter, new RemoteTransition(testRemote, "Test")); + transitions.registerRemoteForTakeover( + filter, new RemoteTransition(testTakeoverRemote, "Test")); + mMainExecutor.flushAll(); + + // Takeover shouldn't happen when the flag is disabled. + setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY); + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */)); + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + assertEquals(0, mDefaultHandler.activeCount()); + assertFalse(takeoverRemoteCalled[0]); + mDefaultHandler.finishAll(); + mMainExecutor.flushAll(); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any()); + + // Takeover should happen when the flag is enabled. + setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */)); + info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + assertEquals(0, mDefaultHandler.activeCount()); + assertTrue(takeoverRemoteCalled[0]); + mDefaultHandler.finishAll(); + mMainExecutor.flushAll(); + verify(mOrganizer, times(2)).finishTransition(eq(transitToken), any()); + } + + @Test public void testOneShotRemoteHandler() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; + final boolean[] takeoverRemoteCalled = new boolean[]{false}; final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); IRemoteTransition testRemote = new RemoteTransitionStub() { @Override @@ -489,12 +582,22 @@ public class ShellTransitionTests extends ShellTestCase { remoteCalled[0] = true; finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */); } + + @Override + public void takeOverAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction startTransaction, + IRemoteTransitionFinishedCallback finishCallback, WindowAnimationState[] states) + throws RemoteException { + takeoverRemoteCalled[0] = true; + finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */); + } }; final int transitType = TRANSIT_FIRST_CUSTOM + 1; OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, new RemoteTransition(testRemote, "Test")); + // Verify that it responds to the remote but not other things. IBinder transitToken = new Binder(); assertNotNull(oneShot.handleRequest(transitToken, @@ -505,6 +608,7 @@ public class ShellTransitionTests extends ShellTestCase { Transitions.TransitionFinishCallback testFinish = mock(Transitions.TransitionFinishCallback.class); + // Verify that it responds to animation properly oneShot.setTransition(transitToken); IBinder anotherToken = new Binder(); @@ -514,6 +618,16 @@ public class ShellTransitionTests extends ShellTestCase { assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0), new StubTransaction(), new StubTransaction(), testFinish)); + assertTrue(remoteCalled[0]); + + // Verify that it handles takeovers properly + IBinder newToken = new Binder(); + oneShot.setTransition(newToken); + assertFalse(oneShot.takeOverAnimation(transitToken, new TransitionInfo(transitType, 0), + new StubTransaction(), testFinish, new WindowAnimationState[0])); + assertTrue(oneShot.takeOverAnimation(newToken, new TransitionInfo(transitType, 0), + new StubTransaction(), testFinish, new WindowAnimationState[0])); + assertTrue(takeoverRemoteCalled[0]); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 8e9619dc1430..7d19f3cbf659 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -28,6 +28,9 @@ import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.os.Handler import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -97,6 +100,10 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Rule val setFlagsRule = SetFlagsRule() + @JvmField + @Rule + val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @Mock private lateinit var mockDesktopModeWindowDecorFactory: DesktopModeWindowDecoration.Factory @Mock private lateinit var mockMainHandler: Handler @@ -306,6 +313,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING) fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() { val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) val decoration = setUpMockDecorationForTask(task) @@ -326,6 +334,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING) fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() { val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) val decoration = setUpMockDecorationForTask(task) @@ -346,6 +355,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING) fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() { val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) val decoration = setUpMockDecorationForTask(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index f9b5882f7ad5..608f74b95280 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND; import static com.google.common.truth.Truth.assertThat; @@ -44,6 +45,7 @@ import android.view.Choreographer; import android.view.Display; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; +import android.view.WindowManager; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -187,7 +189,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false); - assertThat(relayoutParams.mAllowCaptionInputFallthrough).isTrue(); + assertThat(relayoutParams.hasInputFeatureSpy()).isTrue(); } @Test @@ -204,7 +206,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false); - assertThat(relayoutParams.mAllowCaptionInputFallthrough).isFalse(); + assertThat(relayoutParams.hasInputFeatureSpy()).isFalse(); } @Test @@ -220,7 +222,55 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false); - assertThat(relayoutParams.mAllowCaptionInputFallthrough).isFalse(); + assertThat(relayoutParams.hasInputFeatureSpy()).isFalse(); + } + + @Test + public void updateRelayoutParams_freeform_inputChannelNeeded() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); + + assertThat(hasNoInputChannelFeature(relayoutParams)).isFalse(); + } + + @Test + public void updateRelayoutParams_fullscreen_inputChannelNotNeeded() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); + + assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue(); + } + + @Test + public void updateRelayoutParams_multiwindow_inputChannelNotNeeded() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); + + assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue(); } private void fillRoundedCornersResources(int fillValue) { @@ -268,4 +318,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { return taskInfo; } + + private static boolean hasNoInputChannelFeature(RelayoutParams params) { + return (params.mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) + != 0; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt index 847c2dd77d0a..87425915fbf7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt @@ -15,8 +15,8 @@ */ package com.android.wm.shell.windowdecor +import android.graphics.Bitmap import android.graphics.Rect -import android.graphics.drawable.Drawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Display @@ -60,7 +60,7 @@ class ResizeVeilTest : ShellTestCase() { @Mock private lateinit var mockDisplayController: DisplayController @Mock - private lateinit var mockAppIcon: Drawable + private lateinit var mockAppIcon: Bitmap @Mock private lateinit var mockDisplay: Display @Mock diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index a9f44929fc64..48ac1e5717aa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -197,7 +197,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { rectAfterEnd.top += 20 rectAfterEnd.bottom += 20 - verify(mockDesktopWindowDecoration, never()).createResizeVeil() + verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> return@argThat wct.changes.any { (token, change) -> diff --git a/libs/hostgraphics/ADisplay.cpp b/libs/hostgraphics/ADisplay.cpp index 9cc1f40184e3..58fa08281a61 100644 --- a/libs/hostgraphics/ADisplay.cpp +++ b/libs/hostgraphics/ADisplay.cpp @@ -94,14 +94,14 @@ namespace android { int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { // This is running on host, so there are no physical displays available. // Create 1 fake display instead. - DisplayImpl** const impls = reinterpret_cast<DisplayImpl**>( - malloc(sizeof(DisplayImpl*) + sizeof(DisplayImpl))); + DisplayImpl** const impls = + reinterpret_cast<DisplayImpl**>(malloc(sizeof(DisplayImpl*) + sizeof(DisplayImpl))); DisplayImpl* const displayData = reinterpret_cast<DisplayImpl*>(impls + 1); - displayData[0] = DisplayImpl{ADisplayType::DISPLAY_TYPE_INTERNAL, - ADataSpace::ADATASPACE_UNKNOWN, - AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, - DisplayConfigImpl()}; + displayData[0] = + DisplayImpl{ADisplayType::DISPLAY_TYPE_INTERNAL, ADataSpace::ADATASPACE_UNKNOWN, + AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + DisplayConfigImpl()}; impls[0] = displayData; *outDisplays = reinterpret_cast<ADisplay**>(impls); return 1; diff --git a/libs/hostgraphics/Fence.cpp b/libs/hostgraphics/Fence.cpp index 9e54816651c4..4383bf02a00e 100644 --- a/libs/hostgraphics/Fence.cpp +++ b/libs/hostgraphics/Fence.cpp @@ -20,4 +20,4 @@ namespace android { const sp<Fence> Fence::NO_FENCE = sp<Fence>(new Fence); -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/hostgraphics/HostBufferQueue.cpp b/libs/hostgraphics/HostBufferQueue.cpp index ec304378c6c4..7e14b88a47fa 100644 --- a/libs/hostgraphics/HostBufferQueue.cpp +++ b/libs/hostgraphics/HostBufferQueue.cpp @@ -15,18 +15,26 @@ */ #include <gui/BufferQueue.h> +#include <system/window.h> namespace android { class HostBufferQueue : public IGraphicBufferProducer, public IGraphicBufferConsumer { public: - HostBufferQueue() : mWidth(0), mHeight(0) { } + HostBufferQueue() : mWidth(0), mHeight(0) {} - virtual status_t setConsumerIsProtected(bool isProtected) { return OK; } + // Consumer + virtual status_t setConsumerIsProtected(bool isProtected) { + return OK; + } - virtual status_t detachBuffer(int slot) { return OK; } + virtual status_t detachBuffer(int slot) { + return OK; + } - virtual status_t getReleasedBuffers(uint64_t* slotMask) { return OK; } + virtual status_t getReleasedBuffers(uint64_t* slotMask) { + return OK; + } virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) { mWidth = w; @@ -35,22 +43,54 @@ public: return OK; } - virtual status_t setDefaultBufferFormat(PixelFormat defaultFormat) { return OK; } + virtual status_t setDefaultBufferFormat(PixelFormat defaultFormat) { + return OK; + } - virtual status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace) { return OK; } + virtual status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace) { + return OK; + } - virtual status_t discardFreeBuffers() { return OK; } + virtual status_t discardFreeBuffers() { + return OK; + } virtual status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen, - uint64_t maxFrameNumber = 0) { + uint64_t maxFrameNumber = 0) { buffer->mGraphicBuffer = mBuffer; buffer->mSlot = 0; return OK; } - virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) { return OK; } + virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) { + return OK; + } + + virtual status_t setConsumerUsageBits(uint64_t usage) { + return OK; + } + + // Producer + virtual int query(int what, int* value) { + switch (what) { + case NATIVE_WINDOW_WIDTH: + *value = mWidth; + break; + case NATIVE_WINDOW_HEIGHT: + *value = mHeight; + break; + default: + *value = 0; + break; + } + return OK; + } + + virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) { + *buf = mBuffer; + return OK; + } - virtual status_t setConsumerUsageBits(uint64_t usage) { return OK; } private: sp<GraphicBuffer> mBuffer; uint32_t mWidth; @@ -58,8 +98,7 @@ private: }; void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer, - sp<IGraphicBufferConsumer>* outConsumer) { - + sp<IGraphicBufferConsumer>* outConsumer) { sp<HostBufferQueue> obj(new HostBufferQueue()); *outProducer = obj; diff --git a/libs/hostgraphics/PublicFormat.cpp b/libs/hostgraphics/PublicFormat.cpp index af6d2738c801..2a2eec63467c 100644 --- a/libs/hostgraphics/PublicFormat.cpp +++ b/libs/hostgraphics/PublicFormat.cpp @@ -30,4 +30,4 @@ PublicFormat mapHalFormatDataspaceToPublicFormat(int format, android_dataspace d return static_cast<PublicFormat>(format); } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/hostgraphics/gui/BufferItem.h b/libs/hostgraphics/gui/BufferItem.h index 01409e19c715..e95a9231dfaf 100644 --- a/libs/hostgraphics/gui/BufferItem.h +++ b/libs/hostgraphics/gui/BufferItem.h @@ -17,16 +17,15 @@ #ifndef ANDROID_GUI_BUFFERITEM_H #define ANDROID_GUI_BUFFERITEM_H +#include <system/graphics.h> #include <ui/Fence.h> #include <ui/Rect.h> - -#include <system/graphics.h> - #include <utils/StrongPointer.h> namespace android { class Fence; + class GraphicBuffer; // The only thing we need here for layoutlib is mGraphicBuffer. The rest of the fields are added @@ -37,6 +36,7 @@ public: enum { INVALID_BUFFER_SLOT = -1 }; BufferItem() : mGraphicBuffer(nullptr), mFence(Fence::NO_FENCE) {} + ~BufferItem() {} sp<GraphicBuffer> mGraphicBuffer; @@ -60,6 +60,6 @@ public: bool mTransformToDisplayInverse; }; -} +} // namespace android #endif // ANDROID_GUI_BUFFERITEM_H diff --git a/libs/hostgraphics/gui/BufferItemConsumer.h b/libs/hostgraphics/gui/BufferItemConsumer.h index 707b313eb102..c25941151800 100644 --- a/libs/hostgraphics/gui/BufferItemConsumer.h +++ b/libs/hostgraphics/gui/BufferItemConsumer.h @@ -17,32 +17,30 @@ #ifndef ANDROID_GUI_BUFFERITEMCONSUMER_H #define ANDROID_GUI_BUFFERITEMCONSUMER_H -#include <utils/RefBase.h> - #include <gui/ConsumerBase.h> #include <gui/IGraphicBufferConsumer.h> +#include <utils/RefBase.h> namespace android { class BufferItemConsumer : public ConsumerBase { public: - BufferItemConsumer( - const sp<IGraphicBufferConsumer>& consumer, - uint64_t consumerUsage, - int bufferCount, - bool controlledByApp) : mConsumer(consumer) { - } + BufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage, + int bufferCount, bool controlledByApp) + : mConsumer(consumer) {} - status_t acquireBuffer(BufferItem *item, nsecs_t presentWhen, bool waitForFence = true) { + status_t acquireBuffer(BufferItem* item, nsecs_t presentWhen, bool waitForFence = true) { return mConsumer->acquireBuffer(item, presentWhen, 0); } - status_t releaseBuffer( - const BufferItem &item, const sp<Fence>& releaseFence = Fence::NO_FENCE) { return OK; } + status_t releaseBuffer(const BufferItem& item, + const sp<Fence>& releaseFence = Fence::NO_FENCE) { + return OK; + } - void setName(const String8& name) { } + void setName(const String8& name) {} - void setFrameAvailableListener(const wp<FrameAvailableListener>& listener) { } + void setFrameAvailableListener(const wp<FrameAvailableListener>& listener) {} status_t setDefaultBufferSize(uint32_t width, uint32_t height) { return mConsumer->setDefaultBufferSize(width, height); @@ -56,16 +54,23 @@ public: return mConsumer->setDefaultBufferDataSpace(defaultDataSpace); } - void abandon() { } + void abandon() {} - status_t detachBuffer(int slot) { return OK; } + status_t detachBuffer(int slot) { + return OK; + } + + status_t discardFreeBuffers() { + return OK; + } - status_t discardFreeBuffers() { return OK; } + void freeBufferLocked(int slotIndex) {} - void freeBufferLocked(int slotIndex) { } + status_t addReleaseFenceLocked(int slot, const sp<GraphicBuffer> graphicBuffer, + const sp<Fence>& fence) { + return OK; + } - status_t addReleaseFenceLocked( - int slot, const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) { return OK; } private: sp<IGraphicBufferConsumer> mConsumer; }; diff --git a/libs/hostgraphics/gui/BufferQueue.h b/libs/hostgraphics/gui/BufferQueue.h index aa3e7268e11c..67a8c00fd267 100644 --- a/libs/hostgraphics/gui/BufferQueue.h +++ b/libs/hostgraphics/gui/BufferQueue.h @@ -29,7 +29,7 @@ public: enum { NO_BUFFER_AVAILABLE = IGraphicBufferConsumer::NO_BUFFER_AVAILABLE }; static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer, - sp<IGraphicBufferConsumer>* outConsumer); + sp<IGraphicBufferConsumer>* outConsumer); }; } // namespace android diff --git a/libs/hostgraphics/gui/ConsumerBase.h b/libs/hostgraphics/gui/ConsumerBase.h index 9002953c0848..7f7309e8a3a8 100644 --- a/libs/hostgraphics/gui/ConsumerBase.h +++ b/libs/hostgraphics/gui/ConsumerBase.h @@ -18,7 +18,6 @@ #define ANDROID_GUI_CONSUMERBASE_H #include <gui/BufferItem.h> - #include <utils/RefBase.h> namespace android { @@ -28,10 +27,11 @@ public: struct FrameAvailableListener : public virtual RefBase { // See IConsumerListener::onFrame{Available,Replaced} virtual void onFrameAvailable(const BufferItem& item) = 0; + virtual void onFrameReplaced(const BufferItem& /* item */) {} }; }; } // namespace android -#endif // ANDROID_GUI_CONSUMERBASE_H
\ No newline at end of file +#endif // ANDROID_GUI_CONSUMERBASE_H diff --git a/libs/hostgraphics/gui/IGraphicBufferConsumer.h b/libs/hostgraphics/gui/IGraphicBufferConsumer.h index 9eb67b218800..14ac4fe71cc8 100644 --- a/libs/hostgraphics/gui/IGraphicBufferConsumer.h +++ b/libs/hostgraphics/gui/IGraphicBufferConsumer.h @@ -16,16 +16,16 @@ #pragma once -#include <utils/RefBase.h> - #include <ui/PixelFormat.h> - #include <utils/Errors.h> +#include <utils/RefBase.h> namespace android { class BufferItem; + class Fence; + class GraphicBuffer; class IGraphicBufferConsumer : virtual public RefBase { @@ -62,4 +62,4 @@ public: virtual status_t discardFreeBuffers() = 0; }; -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/hostgraphics/gui/IGraphicBufferProducer.h b/libs/hostgraphics/gui/IGraphicBufferProducer.h index a1efd0bcfa4c..8fd8590d10d7 100644 --- a/libs/hostgraphics/gui/IGraphicBufferProducer.h +++ b/libs/hostgraphics/gui/IGraphicBufferProducer.h @@ -17,9 +17,8 @@ #ifndef ANDROID_GUI_IGRAPHICBUFFERPRODUCER_H #define ANDROID_GUI_IGRAPHICBUFFERPRODUCER_H -#include <utils/RefBase.h> - #include <ui/GraphicBuffer.h> +#include <utils/RefBase.h> namespace android { @@ -31,6 +30,10 @@ public: // Disconnect any API originally connected from the process calling disconnect. AllLocal }; + + virtual int query(int what, int* value) = 0; + + virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0; }; } // namespace android diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h index 36d8fba0d61a..2774f89cb54c 100644 --- a/libs/hostgraphics/gui/Surface.h +++ b/libs/hostgraphics/gui/Surface.h @@ -17,25 +17,36 @@ #ifndef ANDROID_GUI_SURFACE_H #define ANDROID_GUI_SURFACE_H -#include <gui/IGraphicBufferProducer.h> +#include <system/window.h> #include <ui/ANativeObjectBase.h> #include <utils/RefBase.h> -#include <system/window.h> + +#include "gui/IGraphicBufferProducer.h" namespace android { class Surface : public ANativeObjectBase<ANativeWindow, Surface, RefBase> { public: - explicit Surface(const sp<IGraphicBufferProducer>& bufferProducer, - bool controlledByApp = false) { + explicit Surface(const sp<IGraphicBufferProducer>& bufferProducer, bool controlledByApp = false) + : mBufferProducer(bufferProducer) { ANativeWindow::perform = hook_perform; + ANativeWindow::dequeueBuffer = hook_dequeueBuffer; + ANativeWindow::query = hook_query; } - static bool isValid(const sp<Surface>& surface) { return surface != nullptr; } + + static bool isValid(const sp<Surface>& surface) { + return surface != nullptr; + } + void allocateBuffers() {} - uint64_t getNextFrameNumber() const { return 0; } + uint64_t getNextFrameNumber() const { + return 0; + } - int setScalingMode(int mode) { return 0; } + int setScalingMode(int mode) { + return 0; + } virtual int disconnect(int api, IGraphicBufferProducer::DisconnectMode mode = @@ -47,24 +58,88 @@ public: // TODO: implement this return 0; } - virtual int unlockAndPost() { return 0; } - virtual int query(int what, int* value) const { return 0; } + + virtual int unlockAndPost() { + return 0; + } + + virtual int query(int what, int* value) const { + return mBufferProducer->query(what, value); + } + + status_t setDequeueTimeout(nsecs_t timeout) { + return OK; + } + + nsecs_t getLastDequeueStartTime() const { + return 0; + } virtual void destroy() {} - int getBuffersDataSpace() { return 0; } + int getBuffersDataSpace() { + return 0; + } protected: virtual ~Surface() {} - static int hook_perform(ANativeWindow* window, int operation, ...) { return 0; } + static int hook_perform(ANativeWindow* window, int operation, ...) { + va_list args; + va_start(args, operation); + Surface* c = getSelf(window); + int result = c->perform(operation, args); + va_end(args); + return result; + } + + static int hook_query(const ANativeWindow* window, int what, int* value) { + const Surface* c = getSelf(window); + return c->query(what, value); + } + + static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, + int* fenceFd) { + Surface* c = getSelf(window); + return c->dequeueBuffer(buffer, fenceFd); + } + + virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { + mBufferProducer->requestBuffer(0, &mBuffer); + *buffer = mBuffer.get(); + return OK; + } + + virtual int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { + return 0; + } + + virtual int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { + return 0; + } + + virtual int perform(int operation, va_list args) { + return 0; + } + + virtual int setSwapInterval(int interval) { + return 0; + } + + virtual int setBufferCount(int bufferCount) { + return 0; + } private: // can't be copied Surface& operator=(const Surface& rhs); + Surface(const Surface& rhs); + + const sp<IGraphicBufferProducer> mBufferProducer; + sp<GraphicBuffer> mBuffer; }; } // namespace android -#endif // ANDROID_GUI_SURFACE_H +#endif // ANDROID_GUI_SURFACE_H diff --git a/libs/hostgraphics/ui/Fence.h b/libs/hostgraphics/ui/Fence.h index 04d535c3a211..187c3116f61c 100644 --- a/libs/hostgraphics/ui/Fence.h +++ b/libs/hostgraphics/ui/Fence.h @@ -17,8 +17,8 @@ #ifndef ANDROID_FENCE_H #define ANDROID_FENCE_H -#include <utils/String8.h> #include <utils/RefBase.h> +#include <utils/String8.h> typedef int64_t nsecs_t; @@ -26,11 +26,14 @@ namespace android { class Fence : public LightRefBase<Fence> { public: - Fence() { } - Fence(int) { } + Fence() {} + + Fence(int) {} + static const sp<Fence> NO_FENCE; static constexpr nsecs_t SIGNAL_TIME_PENDING = INT64_MAX; static constexpr nsecs_t SIGNAL_TIME_INVALID = -1; + static sp<Fence> merge(const char* name, const sp<Fence>& f1, const sp<Fence>& f2) { return NO_FENCE; } @@ -40,16 +43,22 @@ public: } enum class Status { - Invalid, // Fence is invalid - Unsignaled, // Fence is valid but has not yet signaled - Signaled, // Fence is valid and has signaled + Invalid, // Fence is invalid + Unsignaled, // Fence is valid but has not yet signaled + Signaled, // Fence is valid and has signaled }; - status_t wait(int timeout) { return OK; } + status_t wait(int timeout) { + return OK; + } - status_t waitForever(const char* logname) { return OK; } + status_t waitForever(const char* logname) { + return OK; + } - int dup() const { return 0; } + int dup() const { + return 0; + } inline Status getStatus() { // The sync_wait call underlying wait() has been measured to be diff --git a/libs/hostgraphics/ui/GraphicBuffer.h b/libs/hostgraphics/ui/GraphicBuffer.h index ac88e44dbc65..cda45e4660ca 100644 --- a/libs/hostgraphics/ui/GraphicBuffer.h +++ b/libs/hostgraphics/ui/GraphicBuffer.h @@ -19,31 +19,51 @@ #include <stdint.h> #include <sys/types.h> - -#include <vector> - +#include <ui/ANativeObjectBase.h> #include <ui/PixelFormat.h> #include <ui/Rect.h> - #include <utils/RefBase.h> +#include <vector> + namespace android { -class GraphicBuffer : virtual public RefBase { +class GraphicBuffer : public ANativeObjectBase<ANativeWindowBuffer, GraphicBuffer, RefBase> { public: - GraphicBuffer(uint32_t w, uint32_t h):width(w),height(h) { - data.resize(w*h); + GraphicBuffer(uint32_t w, uint32_t h) { + data.resize(w * h); + reserved[0] = data.data(); + width = w; + height = h; + } + + uint32_t getWidth() const { + return static_cast<uint32_t>(width); + } + + uint32_t getHeight() const { + return static_cast<uint32_t>(height); + } + + uint32_t getStride() const { + return static_cast<uint32_t>(width); + } + + uint64_t getUsage() const { + return 0; } - uint32_t getWidth() const { return static_cast<uint32_t>(width); } - uint32_t getHeight() const { return static_cast<uint32_t>(height); } - uint32_t getStride() const { return static_cast<uint32_t>(width); } - uint64_t getUsage() const { return 0; } - PixelFormat getPixelFormat() const { return PIXEL_FORMAT_RGBA_8888; } - //uint32_t getLayerCount() const { return static_cast<uint32_t>(layerCount); } - Rect getBounds() const { return Rect(width, height); } - status_t lockAsyncYCbCr(uint32_t inUsage, const Rect& rect, - android_ycbcr *ycbcr, int fenceFd) { return OK; } + PixelFormat getPixelFormat() const { + return PIXEL_FORMAT_RGBA_8888; + } + + Rect getBounds() const { + return Rect(width, height); + } + + status_t lockAsyncYCbCr(uint32_t inUsage, const Rect& rect, android_ycbcr* ycbcr, int fenceFd) { + return OK; + } status_t lockAsync(uint32_t inUsage, const Rect& rect, void** vaddr, int fenceFd, int32_t* outBytesPerPixel = nullptr, int32_t* outBytesPerStride = nullptr) { @@ -51,11 +71,11 @@ public: return OK; } - status_t unlockAsync(int *fenceFd) { return OK; } + status_t unlockAsync(int* fenceFd) { + return OK; + } private: - uint32_t width; - uint32_t height; std::vector<uint32_t> data; }; diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp index c67b135855f7..5a45ad9085e7 100644 --- a/libs/hwui/SkiaInterpolator.cpp +++ b/libs/hwui/SkiaInterpolator.cpp @@ -20,6 +20,7 @@ #include "include/core/SkTypes.h" #include <cstdlib> +#include <cstring> #include <log/log.h> typedef int Dot14; diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp index 3ebf7d19202d..0a30c6c14c4c 100644 --- a/libs/hwui/effects/GainmapRenderer.cpp +++ b/libs/hwui/effects/GainmapRenderer.cpp @@ -32,6 +32,8 @@ #include "src/core/SkColorFilterPriv.h" #include "src/core/SkImageInfoPriv.h" #include "src/core/SkRuntimeEffectPriv.h" + +#include <cmath> #endif namespace android::uirenderer { @@ -206,12 +208,12 @@ private: void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) { - const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR), - sk_float_log(gainmapInfo.fGainmapRatioMin.fG), - sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); - const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR), - sk_float_log(gainmapInfo.fGainmapRatioMax.fG), - sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); + const SkColor4f logRatioMin({std::log(gainmapInfo.fGainmapRatioMin.fR), + std::log(gainmapInfo.fGainmapRatioMin.fG), + std::log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); + const SkColor4f logRatioMax({std::log(gainmapInfo.fGainmapRatioMax.fR), + std::log(gainmapInfo.fGainmapRatioMax.fG), + std::log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f && gainmapInfo.fGainmapGamma.fG == 1.f && gainmapInfo.fGainmapGamma.fB == 1.f; @@ -248,10 +250,10 @@ private: float W = 0.f; if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) { if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) { - W = (sk_float_log(targetHdrSdrRatio) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)) / - (sk_float_log(mGainmapInfo.fDisplayRatioHdr) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)); + W = (std::log(targetHdrSdrRatio) - + std::log(mGainmapInfo.fDisplayRatioSdr)) / + (std::log(mGainmapInfo.fDisplayRatioHdr) - + std::log(mGainmapInfo.fDisplayRatioSdr)); } else { W = 1.f; } diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index b87002371775..8e07a2f31de1 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -15,14 +15,18 @@ */ #include "ShaderCache.h" + #include <GrDirectContext.h> #include <SkData.h> #include <gui/TraceUtils.h> #include <log/log.h> #include <openssl/sha.h> + #include <algorithm> #include <array> +#include <mutex> #include <thread> + #include "FileBlobCache.h" #include "Properties.h" diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 66e089627a7b..8bb11badb607 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -1010,7 +1010,15 @@ void CanvasContext::destroyHardwareResources() { } void CanvasContext::onContextDestroyed() { - destroyHardwareResources(); + // We don't want to destroyHardwareResources as that will invalidate display lists which + // the client may not be expecting. Instead just purge all scratch resources + if (mRenderPipeline->isContextReady()) { + freePrefetchedLayers(); + for (const sp<RenderNode>& node : mRenderNodes) { + node->destroyLayers(); + } + mRenderPipeline->onDestroyHardwareResources(); + } } DeferredLayerUpdater* CanvasContext::createTextureLayer() { diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index 6993d5240187..7a155c583fd4 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -45,7 +45,7 @@ void HintSessionWrapper::HintSessionBinding::init() { LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!"); BIND_APH_METHOD(getManager); - BIND_APH_METHOD(createSession); + BIND_APH_METHOD(createSessionInternal); BIND_APH_METHOD(closeSession); BIND_APH_METHOD(updateTargetWorkDuration); BIND_APH_METHOD(reportActualWorkDuration); @@ -122,7 +122,8 @@ bool HintSessionWrapper::init() { int64_t targetDurationNanos = mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration; mHintSessionFuture = CommonPool::async([=, this, tids = mPermanentSessionTids] { - return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos); + return mBinding->createSessionInternal(manager, tids.data(), tids.size(), + targetDurationNanos, SessionTag::HWUI); }); return false; } diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h index 14e7a53fd94f..859cc57dea9f 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.h +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -17,6 +17,7 @@ #pragma once #include <android/performance_hint.h> +#include <private/performance_hint_private.h> #include <future> #include <optional> @@ -80,9 +81,10 @@ private: virtual ~HintSessionBinding() = default; virtual void init(); APerformanceHintManager* (*getManager)(); - APerformanceHintSession* (*createSession)(APerformanceHintManager* manager, - const int32_t* tids, size_t tidCount, - int64_t defaultTarget) = nullptr; + APerformanceHintSession* (*createSessionInternal)(APerformanceHintManager* manager, + const int32_t* tids, size_t tidCount, + int64_t defaultTarget, + SessionTag tag) = nullptr; void (*closeSession)(APerformanceHintSession* session) = nullptr; void (*updateTargetWorkDuration)(APerformanceHintSession* session, int64_t targetDuration) = nullptr; diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index a8e85475aff0..0f29613cad33 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -322,11 +322,16 @@ bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& window return false; } - err = native_window_set_buffer_count(window, windowInfo.bufferCount); - if (err != 0) { - ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%zu) failed: %s (%d)", - windowInfo.bufferCount, strerror(-err), err); - return false; + // If bufferCount == 1 then we're in shared buffer mode and we cannot actually call + // set_buffer_count, it'll just fail. + if (windowInfo.bufferCount > 1) { + err = native_window_set_buffer_count(window, windowInfo.bufferCount); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%zu) failed: %s " + "(%d)", + windowInfo.bufferCount, strerror(-err), err); + return false; + } } err = native_window_set_usage(window, windowInfo.windowUsageFlags); diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp index c16602c29e2a..a8db0f4aa4f0 100644 --- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp +++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp @@ -52,8 +52,8 @@ protected: void init() override; MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ()); - MOCK_METHOD(APerformanceHintSession*, fakeCreateSession, - (APerformanceHintManager*, const int32_t*, size_t, int64_t)); + MOCK_METHOD(APerformanceHintSession*, fakeCreateSessionInternal, + (APerformanceHintManager*, const int32_t*, size_t, int64_t, SessionTag)); MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*)); MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t)); MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t)); @@ -72,22 +72,28 @@ protected: // Must be static so we can point to them as normal fn pointers with HintSessionBinding static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); }; - static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager, - const int32_t* ids, size_t idsSize, - int64_t initialTarget) { - return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); + static APerformanceHintSession* stubCreateSessionInternal(APerformanceHintManager* manager, + const int32_t* ids, size_t idsSize, + int64_t initialTarget, + SessionTag tag) { + return sMockBinding->fakeCreateSessionInternal(manager, ids, idsSize, initialTarget, + SessionTag::HWUI); } - static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager, - const int32_t* ids, size_t idsSize, - int64_t initialTarget) { + static APerformanceHintSession* stubManagedCreateSessionInternal( + APerformanceHintManager* manager, const int32_t* ids, size_t idsSize, + int64_t initialTarget, SessionTag tag) { sMockBinding->allowCreationToFinish.get_future().wait(); - return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); + return sMockBinding->fakeCreateSessionInternal(manager, ids, idsSize, initialTarget, + SessionTag::HWUI); } - static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager, - const int32_t* ids, size_t idsSize, - int64_t initialTarget) { + static APerformanceHintSession* stubSlowCreateSessionInternal(APerformanceHintManager* manager, + const int32_t* ids, + size_t idsSize, + int64_t initialTarget, + SessionTag tag) { std::this_thread::sleep_for(50ms); - return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); + return sMockBinding->fakeCreateSessionInternal(manager, ids, idsSize, initialTarget, + SessionTag::HWUI); } static void stubCloseSession(APerformanceHintSession* session) { sMockBinding->fakeCloseSession(session); @@ -139,14 +145,14 @@ void HintSessionWrapperTests::SetUp() { mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId); mWrapper->mBinding = sMockBinding; EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr)); - ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr)); + ON_CALL(*sMockBinding, fakeCreateSessionInternal).WillByDefault(Return(sessionPtr)); ON_CALL(*sMockBinding, fakeSetThreads).WillByDefault(Return(0)); } void HintSessionWrapperTests::MockHintSessionBinding::init() { sMockBinding->getManager = &stubGetManager; - if (sMockBinding->createSession == nullptr) { - sMockBinding->createSession = &stubCreateSession; + if (sMockBinding->createSessionInternal == nullptr) { + sMockBinding->createSessionInternal = &stubCreateSessionInternal; } sMockBinding->closeSession = &stubCloseSession; sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration; @@ -163,14 +169,14 @@ void HintSessionWrapperTests::TearDown() { TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) { EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); - sMockBinding->createSession = stubSlowCreateSession; + sMockBinding->createSessionInternal = stubSlowCreateSessionInternal; mWrapper->init(); mWrapper = nullptr; Mock::VerifyAndClearExpectations(sMockBinding.get()); } TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) { - EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1); + EXPECT_CALL(*sMockBinding, fakeCreateSessionInternal(managerPtr, _, Gt(1), _, _)).Times(1); mWrapper->init(); waitForWrapperReady(); } @@ -219,7 +225,7 @@ TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinish // Here we test whether queueing delayedDestroy works while creation is still happening, if // creation happens after EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); - sMockBinding->createSession = &stubManagedCreateSession; + sMockBinding->createSessionInternal = &stubManagedCreateSessionInternal; // Start creating the session and destroying it at the same time mWrapper->init(); @@ -246,7 +252,7 @@ TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishe // Here we test whether queueing delayedDestroy works while creation is still happening, if // creation happens before EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); - sMockBinding->createSession = &stubManagedCreateSession; + sMockBinding->createSessionInternal = &stubManagedCreateSessionInternal; // Start creating the session and destroying it at the same time mWrapper->init(); @@ -352,7 +358,7 @@ TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct } TEST_F(HintSessionWrapperTests, setThreadsUpdatesSessionThreads) { - EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1); + EXPECT_CALL(*sMockBinding, fakeCreateSessionInternal(managerPtr, _, Gt(1), _, _)).Times(1); EXPECT_CALL(*sMockBinding, fakeSetThreads(sessionPtr, testing::IsSupersetOf({11, 22}))) .Times(1); mWrapper->init(); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index f6c57927cc85..6a560b365247 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -403,7 +403,7 @@ skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) { } static skcms_TransferFunction trfn_apply_gain(const skcms_TransferFunction trfn, float gain) { - float pow_gain_ginv = sk_float_pow(gain, 1 / trfn.g); + float pow_gain_ginv = std::pow(gain, 1 / trfn.g); skcms_TransferFunction result; result.g = trfn.g; result.a = trfn.a * pow_gain_ginv; |