diff options
61 files changed, 1159 insertions, 369 deletions
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 06399b958159..9e5e8deda84b 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1022,6 +1022,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public @interface SizeChangesSupportMode {} /** + * This change id enables compat policy that ignores app requested orientation in + * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See + * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for + * details. + * @hide + */ + @ChangeId + @Overridable + @Disabled + public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION = + 254631730L; // buganizer id + + /** * This change id forces the packages it is applied to never have Display API sandboxing * applied for a letterbox or SCM activity. The Display APIs will continue to provide * DisplayArea bounds. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2f779010be0e..ed9cb00db290 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -814,6 +814,45 @@ public interface WindowManager extends ViewManager { } /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity can be opted-in or opted-out + * from the compatibility treatment that avoids {@link + * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by + * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural + * orientation of the device. + * + * <p>The treatment is disabled by default but device manufacturers can enable the treatment + * using their discretion to improve display compatibility. + * + * <p>With this property set to {@code true}, the system could ignore {@link + * android.app.Activity#setRequestedOrientation} call from an app if one of the following + * conditions are true: + * <ul> + * <li>Activity is relaunching due to the previous {@link + * android.app.Activity#setRequestedOrientation} call. + * <li>Camera compatibility force rotation treatment is active for the package. + * </ul> + * + * <p>Setting this property to {@code false} informs the system that the activity must be + * opted-out from the compatibility treatment even if the device manufacturer has opted the app + * into the treatment. + * + * <p><b>Syntax:</b> + * <pre> + * <activity> + * <property + * android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION" + * android:value="true|false"/> + * </activity> + * </pre> + * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = + "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; + + /** * @hide */ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array"; diff --git a/core/res/OWNERS b/core/res/OWNERS index c54638a368a2..3c143668489d 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -26,6 +26,11 @@ toddke@google.com tsuji@google.com yamasani@google.com +# WindowManager team +# TODO(262451702): Move WindowManager configs out of config.xml in a separate file +per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS +per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS + # Resources finalization per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS per-file res/xml/public-final.xml = file:/tools/aapt2/OWNERS diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a8c2246ef966..f7187c466799 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5328,6 +5328,11 @@ If given value is outside of this range, the option 0 (top) is assummed. --> <integer name="config_letterboxDefaultPositionForTabletopModeReachability">0</integer> + <!-- Whether should ignore app requested orientation in response to an app + calling Activity#setRequestedOrientation. See + LetterboxUiController#shouldIgnoreRequestedOrientation for details. --> + <bool name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled">false</bool> + <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. --> <bool name="config_letterboxIsEducationEnabled">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5c3e1d69c6b5..368ef960f938 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4457,6 +4457,7 @@ <java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" /> <java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" /> <java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" /> + <java-symbol type="bool" name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled" /> <java-symbol type="bool" name="config_letterboxIsEducationEnabled" /> <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" /> <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" /> diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index 13a2c78d463e..d189ae2cf72e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -22,6 +22,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.window.extensions.embedding.SplitAttributes; import org.junit.Before; import org.junit.Test; @@ -53,4 +54,15 @@ public class WindowExtensionsTest { public void testGetActivityEmbeddingComponent() { assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull(); } + + @Test + public void testSplitAttributes_default() { + // Make sure the default value in the extensions aar. + final SplitAttributes splitAttributes = new SplitAttributes.Builder().build(); + assertThat(splitAttributes.getLayoutDirection()) + .isEqualTo(SplitAttributes.LayoutDirection.LOCALE); + assertThat(splitAttributes.getSplitType()) + .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f)); + assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0); + } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 4978e04e0115..84ab4487feee 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_title_color.xml index 1ecc13e4da38..1ecc13e4da38 100644 --- a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml +++ b/libs/WindowManager/Shell/res/color/decor_title_color.xml diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml index 416287d2cbb3..416287d2cbb3 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml index 416287d2cbb3..416287d2cbb3 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml index 582a11cfdb8e..8b4792acba3e 100644 --- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml @@ -20,7 +20,7 @@ android:id="@+id/handle_menu" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" -android:background="@drawable/decor_caption_menu_background"> +android:background="@drawable/desktop_mode_decor_menu_background"> <Button style="@style/CaptionButtonStyle" android:id="@+id/fullscreen_button" diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml index 51e634c17532..2a4cc02f0925 100644 --- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml @@ -16,10 +16,10 @@ --> <com.android.wm.shell.windowdecor.WindowDecorLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/caption" + android:id="@+id/desktop_mode_caption" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@drawable/decor_caption_title"> + android:background="@drawable/desktop_mode_decor_title"> <Button style="@style/CaptionButtonStyle" android:id="@+id/back_button" 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 b714d2e5e1e0..39fe4559c88f 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 @@ -370,8 +370,6 @@ public class Transitions implements RemoteCallable<Transitions> { // If this is a transferred starting window, we want it immediately visible. && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { t.setAlpha(leash, 0.f); - // fix alpha in finish transaction in case the animator itself no-ops. - finishT.setAlpha(leash, 1.f); } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { finishT.hide(leash); 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 b500f5fb0155..b4301577ac10 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 @@ -282,7 +282,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public boolean onTouch(View v, MotionEvent e) { boolean isDrag = false; int id = v.getId(); - if (id != R.id.caption_handle && id != R.id.caption) { + if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) { return false; } if (id == R.id.caption_handle) { 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 467f374f2110..9c2beb9c4b2b 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 @@ -132,7 +132,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; - mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration; + mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width; mRelayoutParams.mShadowRadiusId = shadowRadiusID; @@ -212,7 +212,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Sets up listeners when a new root view is created. */ private void setupRootView() { - View caption = mResult.mRootView.findViewById(R.id.caption); + View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); caption.setOnTouchListener(mOnCaptionTouchListener); View close = caption.findViewById(R.id.close_window); close.setOnClickListener(mOnCaptionButtonClickListener); @@ -243,7 +243,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private void setCaptionVisibility(boolean visible) { int v = visible ? View.VISIBLE : View.GONE; - View captionView = mResult.mRootView.findViewById(R.id.caption); + View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption); captionView.setVisibility(v); if (!visible) closeHandleMenu(); } @@ -265,7 +265,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void setButtonVisibility(boolean visible) { int visibility = visible ? View.VISIBLE : View.GONE; - View caption = mResult.mRootView.findViewById(R.id.caption); + View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); View back = caption.findViewById(R.id.back_button); View close = caption.findViewById(R.id.close_window); back.setVisibility(visibility); @@ -304,7 +304,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); String namePrefix = "Caption Menu"; - mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t, + mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY, width, height); mSyncQueue.runInSync(transaction -> { @@ -336,7 +336,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void closeHandleMenuIfNeeded(MotionEvent ev) { if (isHandleMenuActive()) { - if (!checkEventInCaptionView(ev, R.id.caption)) { + if (!checkEventInCaptionView(ev, R.id.desktop_mode_caption)) { closeHandleMenu(); } } @@ -389,7 +389,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void checkClickEvent(MotionEvent ev) { if (mResult.mRootView == null) return; - View caption = mResult.mRootView.findViewById(R.id.caption); + View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); PointF inputPoint = offsetCaptionLocation(ev); if (!isHandleMenuActive()) { View handle = caption.findViewById(R.id.caption_handle); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index d06fb55a5769..7ec4e21bcfcc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -30,10 +30,13 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static java.lang.Integer.MAX_VALUE; + import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; import android.test.suitebuilder.annotation.SmallTest; @@ -135,12 +138,12 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_addInitCallback() { - verify(mShellInit, times(1)).addInitCallback(any(), any()); + verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipController)); } @Test public void instantiateController_registerDumpCallback() { - verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any()); + verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), eq(mPipController)); } @Test @@ -156,7 +159,7 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_registerExternalInterface() { verify(mShellController, times(1)).addExternalInterface( - eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any()); + eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController)); } @Test @@ -252,6 +255,10 @@ public class PipControllerTest extends ShellTestCase { final int displayId = 1; final Rect bounds = new Rect(0, 0, 10, 10); when(mMockPipBoundsAlgorithm.getDefaultBounds()).thenReturn(bounds); + when(mMockPipBoundsState.getBounds()).thenReturn(bounds); + when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(1, 1)); + when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_VALUE, MAX_VALUE)); + when(mMockPipBoundsState.getBounds()).thenReturn(bounds); when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId); when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1); when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java index fbc50c68eff9..8d92d0864338 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ShellExecutor; @@ -61,10 +62,9 @@ public class ShellControllerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private ShellExecutor mExecutor; - @Mock private Context mTestUserContext; + private TestShellExecutor mExecutor; private ShellController mController; private TestConfigurationChangeListener mConfigChangeListener; private TestKeyguardChangeListener mKeyguardChangeListener; @@ -77,6 +77,7 @@ public class ShellControllerTest extends ShellTestCase { mKeyguardChangeListener = new TestKeyguardChangeListener(); mConfigChangeListener = new TestConfigurationChangeListener(); mUserChangeListener = new TestUserChangeListener(); + mExecutor = new TestShellExecutor(); mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } @@ -104,6 +105,7 @@ public class ShellControllerTest extends ShellTestCase { Bundle b = new Bundle(); mController.asShell().createExternalInterfaces(b); + mExecutor.flushAll(); assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback); } 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 c764741d4cd6..595c3b4880df 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 @@ -936,7 +936,7 @@ public class ShellTransitionTests extends ShellTestCase { TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, RunningTaskInfo taskInfo) { final TransitionInfo.Change change = - new TransitionInfo.Change(null /* token */, null /* leash */); + new TransitionInfo.Change(null /* token */, createMockSurface(true)); change.setMode(mode); change.setTaskInfo(taskInfo); mInfo.addChange(change); @@ -961,7 +961,7 @@ public class ShellTransitionTests extends ShellTestCase { final TransitionInfo.Change mChange; ChangeBuilder(@WindowManager.TransitionType int mode) { - mChange = new TransitionInfo.Change(null /* token */, null /* leash */); + mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true)); mChange.setMode(mode); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java index a5e3a2e76ce5..355072116cb1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java @@ -205,28 +205,32 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400, /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); - int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId(); - - final int taskId = 1; - final ActivityManager.RunningTaskInfo taskInfo = - createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM); - final ActivityManager.RunningTaskInfo secondTaskInfo = - createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM); - final ActivityManager.RunningTaskInfo thirdTaskInfo = - createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM); - - SurfaceControl surfaceControl = mock(SurfaceControl.class); - final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); - final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); - - mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, - finishT); - mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl, - startT, finishT); - mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl, - startT, finishT); - mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo); - mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo); + try { + int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId(); + + final int taskId = 1; + final ActivityManager.RunningTaskInfo taskInfo = + createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM); + final ActivityManager.RunningTaskInfo secondTaskInfo = + createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM); + final ActivityManager.RunningTaskInfo thirdTaskInfo = + createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM); + + SurfaceControl surfaceControl = mock(SurfaceControl.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + + mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, + finishT); + mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl, + startT, finishT); + mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl, + startT, finishT); + mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo); + mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo); + } finally { + secondaryDisplay.release(); + } }); verify(mMockInputMonitorFactory, times(2)).create(any(), any()); verify(mInputMonitor, times(1)).dispose(); @@ -239,7 +243,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { r.run(); latch.countDown(); }); - latch.await(20, TimeUnit.MILLISECONDS); + latch.await(1, TimeUnit.SECONDS); } private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index dd9ab9899e13..ec4f17fd072b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -48,6 +48,7 @@ import android.view.SurfaceControlViewHost; import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManager.LayoutParams; +import android.window.TaskConstants; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -232,7 +233,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT) .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f}); verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10); - verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1); + verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, + TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND); verify(mMockSurfaceControlStartT).show(taskBackgroundSurface); verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); @@ -560,7 +562,8 @@ public class WindowDecorationTests extends ShellTestCase { int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); String name = "Test Window"; WindowDecoration.AdditionalWindow additionalWindow = - addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT, + addWindow(R.layout.desktop_mode_decor_handle_menu, name, + mMockSurfaceControlAddWindowT, x - mRelayoutResult.mDecorContainerOffsetX, y - mRelayoutResult.mDecorContainerOffsetY, width, height); diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java index f6914efd6d83..23d6e34db4f1 100644 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java @@ -22,8 +22,8 @@ import static com.android.server.backup.encryption.protos.nano.ChunksMetadataPro import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java index 096b2da10c98..bfc5d0dca3ff 100644 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java @@ -18,9 +18,9 @@ package com.android.server.backup.encryption.tasks; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java index fa4fef50ac1a..222b88221ba2 100644 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java +++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java @@ -19,8 +19,8 @@ package com.android.server.backup.encryption.tasks; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; @@ -41,13 +41,6 @@ import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; import com.android.server.backup.testing.CryptoTestUtils; -import java.io.IOException; -import java.util.Arrays; -import java.util.Map; -import java.util.Map.Entry; - -import javax.crypto.SecretKey; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -59,6 +52,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; + +import javax.crypto.SecretKey; + + @RunWith(RobolectricTestRunner.class) public class EncryptedKvBackupTaskTest { private static final boolean INCREMENTAL = true; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 1f2297ba3a0c..fc2bf0a9bd93 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -21,10 +21,10 @@ import static android.os.UserHandle.MU_ENABLED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java index 95f7ef41b10b..508dffc2fa21 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java @@ -18,7 +18,7 @@ package com.android.settingslib.net; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java index f28572f5f71d..cf07c6bb7cb9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.app.Activity; @@ -143,7 +143,7 @@ public class EditUserInfoControllerTest { dialog.show(); dialog.cancel(); - verifyZeroInteractions(successCallback); + verifyNoInteractions(successCallback); verify(cancelCallback, times(1)) .run(); } @@ -159,7 +159,7 @@ public class EditUserInfoControllerTest { dialog.show(); dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick(); - verifyZeroInteractions(successCallback); + verifyNoInteractions(successCallback); verify(cancelCallback, times(1)) .run(); } @@ -180,7 +180,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", oldUserIcon); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -198,7 +198,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", null); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -219,7 +219,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept(expectedNewName, mCurrentIcon); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -238,7 +238,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", newPhoto); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test @@ -257,7 +257,7 @@ public class EditUserInfoControllerTest { verify(successCallback, times(1)) .accept("test", newPhoto); - verifyZeroInteractions(cancelCallback); + verifyNoInteractions(cancelCallback); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java index 0b3495def21f..ca0aa0d7b900 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java @@ -56,7 +56,7 @@ public class UpdatableListPreferenceDialogFragmentTest { mUpdatableListPrefDlgFragment = spy(UpdatableListPreferenceDialogFragment .newInstance(KEY, MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES)); - mEntries = spy(new ArrayList<>()); + mEntries = new ArrayList<>(); mUpdatableListPrefDlgFragment.setEntries(mEntries); mUpdatableListPrefDlgFragment .setMetricsCategory(mUpdatableListPrefDlgFragment.getArguments()); diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f0cc42e54131..7d72598c34cf 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -658,7 +658,9 @@ <item>7</item> <!-- WAKE_REASON_WAKE_MOTION --> <item>9</item> <!-- WAKE_REASON_LID --> <item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED --> - <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE --> + <item>15</item> <!-- WAKE_REASON_TAP --> + <item>16</item> <!-- WAKE_REASON_LIFT --> + <item>17</item> <!-- WAKE_REASON_BIOMETRIC --> </integer-array> <!-- Whether the communal service should be enabled --> diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 8cd734ccc7ae..7c4fa6c6d27e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -414,7 +414,13 @@ object Flags { unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true) // TODO(b/255697805): Tracking Bug - @JvmField val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false) + @JvmField + val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false) + + // TODO(b/263826204): Tracking Bug + @JvmField + val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM = + unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true) // 1300 - screenshots // TODO(b/254512719): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 4d914fe0adef..15fed3244d97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -49,9 +49,9 @@ class StatusBarPipelineFlags @Inject constructor(private val featureFlags: Featu featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon() /** - * Returns true if we should apply some coloring to the wifi icon that was rendered with the new + * Returns true if we should apply some coloring to the icons that were rendered with the new * pipeline to help with debugging. */ - fun useWifiDebugColoring(): Boolean = + fun useDebugColoring(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index ab442b5ab4de..3e81c7c7cefd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.binder import android.content.res.ColorStateList import android.view.View import android.view.View.GONE +import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.ImageView @@ -30,7 +31,13 @@ import com.android.settingslib.graph.SignalDrawable import com.android.systemui.R import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT +import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -40,7 +47,8 @@ object MobileIconBinder { fun bind( view: ViewGroup, viewModel: LocationBasedMobileViewModel, - ) { + ): ModernStatusBarViewBinding { + val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group) val activityContainer = view.requireViewById<View>(R.id.inout_container) val activityIn = view.requireViewById<ImageView>(R.id.mobile_in) val activityOut = view.requireViewById<ImageView>(R.id.mobile_out) @@ -49,12 +57,39 @@ object MobileIconBinder { val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) } val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming) val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space) + val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) view.isVisible = true iconView.isVisible = true + // TODO(b/238425913): We should log this visibility state. + @StatusBarIconView.VisibleState + val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN) + + val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + visibilityState.collect { state -> + when (state) { + STATE_ICON -> { + mobileGroupView.visibility = VISIBLE + dotView.visibility = GONE + } + STATE_DOT -> { + mobileGroupView.visibility = INVISIBLE + dotView.visibility = VISIBLE + } + STATE_HIDDEN -> { + mobileGroupView.visibility = INVISIBLE + dotView.visibility = INVISIBLE + } + } + } + } + // Set the icon for the triangle launch { viewModel.iconId.distinctUntilChanged().collect { iconId -> @@ -89,15 +124,43 @@ object MobileIconBinder { // Set the tint launch { - viewModel.tint.collect { tint -> + iconTint.collect { tint -> val tintList = ColorStateList.valueOf(tint) iconView.imageTintList = tintList networkTypeView.imageTintList = tintList roamingView.imageTintList = tintList activityIn.imageTintList = tintList activityOut.imageTintList = tintList + dotView.setDecorColor(tint) } } + + launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } + } + } + + return object : ModernStatusBarViewBinding { + override fun getShouldIconBeVisible(): Boolean { + // If this view model exists, then the icon should be visible. + return true + } + + override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) { + visibilityState.value = state + } + + override fun onIconTintChanged(newTint: Int) { + if (viewModel.useDebugColoring) { + return + } + iconTint.value = newTint + } + + override fun onDecorTintChanged(newTint: Int) { + if (viewModel.useDebugColoring) { + return + } + decorTint.value = newTint } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index e86fee24fe4d..ed9a1884a7b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -17,50 +17,20 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.view import android.content.Context -import android.graphics.Rect import android.util.AttributeSet import android.view.LayoutInflater import com.android.systemui.R -import com.android.systemui.statusbar.BaseStatusBarFrameLayout -import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel -import java.util.ArrayList +import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView class ModernStatusBarMobileView( context: Context, attrs: AttributeSet?, -) : BaseStatusBarFrameLayout(context, attrs) { +) : ModernStatusBarView(context, attrs) { var subId: Int = -1 - private lateinit var slot: String - override fun getSlot() = slot - - override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { - // TODO - } - - override fun setStaticDrawableColor(color: Int) { - // TODO - } - - override fun setDecorColor(color: Int) { - // TODO - } - - override fun setVisibleState(state: Int, animate: Boolean) { - // TODO - } - - override fun getVisibleState(): Int { - return STATE_ICON - } - - override fun isIconVisible(): Boolean { - return true - } - companion object { /** @@ -77,9 +47,8 @@ class ModernStatusBarMobileView( .inflate(R.layout.status_bar_mobile_signal_group_new, null) as ModernStatusBarMobileView) .also { - it.slot = slot it.subId = viewModel.subscriptionId - MobileIconBinder.bind(it, viewModel) + it.initView(slot) { MobileIconBinder.bind(it, viewModel) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt index b0dc41f45488..24cd9304f8dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt @@ -18,11 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import android.graphics.Color import com.android.systemui.statusbar.phone.StatusBarLocation -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOf +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags /** * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This @@ -33,50 +29,51 @@ import kotlinx.coroutines.flow.flowOf */ abstract class LocationBasedMobileViewModel( val commonImpl: MobileIconViewModelCommon, - val logger: ConnectivityPipelineLogger, + statusBarPipelineFlags: StatusBarPipelineFlags, + debugTint: Int, ) : MobileIconViewModelCommon by commonImpl { - abstract val tint: Flow<Int> + val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring() + + val defaultColor: Int = + if (useDebugColoring) { + debugTint + } else { + Color.WHITE + } companion object { fun viewModelForLocation( commonImpl: MobileIconViewModelCommon, - logger: ConnectivityPipelineLogger, + statusBarPipelineFlags: StatusBarPipelineFlags, loc: StatusBarLocation, ): LocationBasedMobileViewModel = when (loc) { - StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger) - StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger) - StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger) + StatusBarLocation.HOME -> + HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) + StatusBarLocation.KEYGUARD -> + KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags) + StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) } } } class HomeMobileIconViewModel( commonImpl: MobileIconViewModelCommon, - logger: ConnectivityPipelineLogger, -) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { - override val tint: Flow<Int> = - flowOf(Color.CYAN) - .distinctUntilChanged() - .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})") -} + statusBarPipelineFlags: StatusBarPipelineFlags, +) : + MobileIconViewModelCommon, + LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN) class QsMobileIconViewModel( commonImpl: MobileIconViewModelCommon, - logger: ConnectivityPipelineLogger, -) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { - override val tint: Flow<Int> = - flowOf(Color.GREEN) - .distinctUntilChanged() - .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})") -} + statusBarPipelineFlags: StatusBarPipelineFlags, +) : + MobileIconViewModelCommon, + LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN) class KeyguardMobileIconViewModel( commonImpl: MobileIconViewModelCommon, - logger: ConnectivityPipelineLogger, -) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { - override val tint: Flow<Int> = - flowOf(Color.MAGENTA) - .distinctUntilChanged() - .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})") -} + statusBarPipelineFlags: StatusBarPipelineFlags, +) : + MobileIconViewModelCommon, + LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index b9318b181aaf..24370d221ade 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants @@ -41,6 +42,7 @@ constructor( private val logger: ConnectivityPipelineLogger, private val constants: ConnectivityConstants, @Application private val scope: CoroutineScope, + private val statusBarPipelineFlags: StatusBarPipelineFlags, ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() @@ -60,7 +62,11 @@ constructor( ) .also { mobileIconSubIdCache[subId] = it } - return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location) + return LocationBasedMobileViewModel.viewModelForLocation( + common, + statusBarPipelineFlags, + location, + ) } private fun removeInvalidModelsFromCache(subIds: List<Int>) { @@ -75,6 +81,7 @@ constructor( private val logger: ConnectivityPipelineLogger, private val constants: ConnectivityConstants, @Application private val scope: CoroutineScope, + private val statusBarPipelineFlags: StatusBarPipelineFlags, ) { fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { return MobileIconsViewModel( @@ -83,6 +90,7 @@ constructor( logger, constants, scope, + statusBarPipelineFlags, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt new file mode 100644 index 000000000000..f67876b50233 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt @@ -0,0 +1,40 @@ +/* + * 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.systemui.statusbar.pipeline.shared.ui.binder + +import com.android.systemui.statusbar.StatusBarIconView + +/** + * Defines interface for an object that acts as the binding between a modern status bar view and its + * view-model. + * + * Users of the view binder classes in the modern status bar pipeline should use this to control the + * binder after it is bound. + */ +interface ModernStatusBarViewBinding { + /** Returns true if the icon should be visible and false otherwise. */ + fun getShouldIconBeVisible(): Boolean + + /** Notifies that the visibility state has changed. */ + fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) + + /** Notifies that the icon tint has been updated. */ + fun onIconTintChanged(newTint: Int) + + /** Notifies that the decor tint has been updated (used only for the dot). */ + fun onDecorTintChanged(newTint: Int) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt new file mode 100644 index 000000000000..cc0ec548716d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -0,0 +1,115 @@ +/* + * 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.systemui.statusbar.pipeline.shared.ui.view + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.Gravity +import com.android.systemui.R +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.statusbar.BaseStatusBarFrameLayout +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT +import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding + +/** + * A new and more modern implementation of [BaseStatusBarFrameLayout] that gets updated by view + * binders communicating via [ModernStatusBarViewBinding]. + */ +open class ModernStatusBarView(context: Context, attrs: AttributeSet?) : + BaseStatusBarFrameLayout(context, attrs) { + + private lateinit var slot: String + private lateinit var binding: ModernStatusBarViewBinding + + @StatusBarIconView.VisibleState + private var iconVisibleState: Int = STATE_HIDDEN + set(value) { + if (field == value) { + return + } + field = value + binding.onVisibilityStateChanged(value) + } + + override fun getSlot() = slot + + override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { + val newTint = DarkIconDispatcher.getTint(areas, this, tint) + binding.onIconTintChanged(newTint) + binding.onDecorTintChanged(newTint) + } + + override fun setStaticDrawableColor(color: Int) { + binding.onIconTintChanged(color) + } + + override fun setDecorColor(color: Int) { + binding.onDecorTintChanged(color) + } + + override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) { + iconVisibleState = state + } + + @StatusBarIconView.VisibleState + override fun getVisibleState(): Int { + return iconVisibleState + } + + override fun isIconVisible(): Boolean { + return binding.getShouldIconBeVisible() + } + + /** + * Initializes this view. + * + * Creates a dot view, and uses [bindingCreator] to get and set the binding. + */ + fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) { + // The dot view requires [slot] to be set, and the [binding] may require an instantiated dot + // view. So, this is the required order. + this.slot = slot + initDotView() + this.binding = bindingCreator.invoke() + } + + /** + * Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view. + * + * Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView] and + * [com.android.systemui.statusbar.StatusBarMobileView]. + */ + private fun initDotView() { + // TODO(b/238425913): Could we just have this dot view be part of the layout with a dot + // drawable so we don't need to inflate it manually? Would that not work with animations? + val dotView = + StatusBarIconView(mContext, slot, null).also { + it.id = R.id.status_bar_dot + // Hard-code this view to always be in the DOT state so that whenever it's visible + // it will show a dot + it.visibleState = STATE_DOT + } + + val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size) + val lp = LayoutParams(width, width) + lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START + addView(dotView, lp) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index cc67c84772a5..2aff12c8721d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel import kotlinx.coroutines.InternalCoroutinesApi @@ -49,31 +50,12 @@ import kotlinx.coroutines.launch @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") object WifiViewBinder { - /** - * Defines interface for an object that acts as the binding between the view and its view-model. - * - * Users of the [WifiViewBinder] class should use this to control the binder after it is bound. - */ - interface Binding { - /** Returns true if the wifi icon should be visible and false otherwise. */ - fun getShouldIconBeVisible(): Boolean - - /** Notifies that the visibility state has changed. */ - fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) - - /** Notifies that the icon tint has been updated. */ - fun onIconTintChanged(newTint: Int) - - /** Notifies that the decor tint has been updated (used only for the dot). */ - fun onDecorTintChanged(newTint: Int) - } - /** Binds the view to the view-model, continuing to update the former based on the latter. */ @JvmStatic fun bind( view: ViewGroup, viewModel: LocationBasedWifiViewModel, - ): Binding { + ): ModernStatusBarViewBinding { val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group) val iconView = view.requireViewById<ImageView>(R.id.wifi_signal) val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) @@ -148,7 +130,7 @@ object WifiViewBinder { } } - return object : Binding { + return object : ModernStatusBarViewBinding { override fun getShouldIconBeVisible(): Boolean { return viewModel.wifiIcon.value is WifiIcon.Visible } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt index be7782c37cfd..7a734862fe1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -16,17 +16,12 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view +import android.annotation.SuppressLint import android.content.Context -import android.graphics.Rect import android.util.AttributeSet -import android.view.Gravity import android.view.LayoutInflater import com.android.systemui.R -import com.android.systemui.plugins.DarkIconDispatcher -import com.android.systemui.statusbar.BaseStatusBarFrameLayout -import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT -import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel @@ -36,83 +31,14 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi */ class ModernStatusBarWifiView( context: Context, - attrs: AttributeSet? -) : BaseStatusBarFrameLayout(context, attrs) { - - private lateinit var slot: String - private lateinit var binding: WifiViewBinder.Binding - - @StatusBarIconView.VisibleState - private var iconVisibleState: Int = STATE_HIDDEN - set(value) { - if (field == value) { - return - } - field = value - binding.onVisibilityStateChanged(value) - } - - override fun getSlot() = slot - - override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { - val newTint = DarkIconDispatcher.getTint(areas, this, tint) - binding.onIconTintChanged(newTint) - binding.onDecorTintChanged(newTint) - } - - override fun setStaticDrawableColor(color: Int) { - binding.onIconTintChanged(color) - } - - override fun setDecorColor(color: Int) { - binding.onDecorTintChanged(color) - } - - override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) { - iconVisibleState = state - } - - @StatusBarIconView.VisibleState - override fun getVisibleState(): Int { - return iconVisibleState - } - - override fun isIconVisible(): Boolean { - return binding.getShouldIconBeVisible() - } - - private fun initView( - slotName: String, - wifiViewModel: LocationBasedWifiViewModel, - ) { - slot = slotName - initDotView() - binding = WifiViewBinder.bind(this, wifiViewModel) - } - - // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView]. - private fun initDotView() { - // TODO(b/238425913): Could we just have this dot view be part of - // R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it - // manually? Would that not work with animations? - val dotView = StatusBarIconView(mContext, slot, null).also { - it.id = R.id.status_bar_dot - // Hard-code this view to always be in the DOT state so that whenever it's visible it - // will show a dot - it.visibleState = STATE_DOT - } - - val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size) - val lp = LayoutParams(width, width) - lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START - addView(dotView, lp) - } - + attrs: AttributeSet?, +) : ModernStatusBarView(context, attrs) { companion object { /** * Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and * returns it. */ + @SuppressLint("InflateParams") @JvmStatic fun constructAndBind( context: Context, @@ -123,7 +49,7 @@ class ModernStatusBarWifiView( LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) as ModernStatusBarWifiView ).also { - it.initView(slot, wifiViewModel) + it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt index a4615cc897cf..02c3a652cc8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt @@ -47,7 +47,7 @@ abstract class LocationBasedWifiViewModel( /** True if the airplane spacer view should be visible. */ val isAirplaneSpacerVisible: Flow<Boolean>, ) { - val useDebugColoring: Boolean = statusBarPipelineFlags.useWifiDebugColoring() + val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring() val defaultColor: Int = if (useDebugColoring) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt new file mode 100644 index 000000000000..a2c1209f5a40 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt @@ -0,0 +1,189 @@ +/* + * 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.systemui.statusbar.pipeline.mobile.ui.view + +import android.content.res.ColorStateList +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.testing.ViewUtils +import android.view.View +import android.widget.ImageView +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@OptIn(ExperimentalCoroutinesApi::class) +class ModernStatusBarMobileViewTest : SysuiTestCase() { + + private lateinit var testableLooper: TestableLooper + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags + @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var constants: ConnectivityConstants + + private lateinit var viewModel: LocationBasedMobileViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + val interactor = FakeMobileIconInteractor(tableLogBuffer) + + val viewModelCommon = + MobileIconViewModel( + subscriptionId = 1, + interactor, + logger, + constants, + testScope.backgroundScope, + ) + viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags) + } + + // Note: The following tests are more like integration tests, since they stand up a full + // [WifiViewModel] and test the interactions between the view, view-binder, and view-model. + + @Test + fun setVisibleState_icon_iconShownDotHidden() { + val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + + view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false) + + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + assertThat(view.getGroupView().visibility).isEqualTo(View.VISIBLE) + assertThat(view.getDotView().visibility).isEqualTo(View.GONE) + + ViewUtils.detachView(view) + } + + @Test + fun setVisibleState_dot_iconHiddenDotShown() { + val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + + view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false) + + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + assertThat(view.getGroupView().visibility).isEqualTo(View.INVISIBLE) + assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE) + + ViewUtils.detachView(view) + } + + @Test + fun setVisibleState_hidden_iconAndDotHidden() { + val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + + view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false) + + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + assertThat(view.getGroupView().visibility).isEqualTo(View.INVISIBLE) + assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE) + + ViewUtils.detachView(view) + } + + @Test + fun isIconVisible_alwaysTrue() { + val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + assertThat(view.isIconVisible).isTrue() + + ViewUtils.detachView(view) + } + + @Test + fun onDarkChanged_iconHasNewColor() { + whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) + val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + val color = 0x12345678 + view.onDarkChanged(arrayListOf(), 1.0f, color) + testableLooper.processAllMessages() + + assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) + + ViewUtils.detachView(view) + } + + @Test + fun setStaticDrawableColor_iconHasNewColor() { + whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) + val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + ViewUtils.attachView(view) + testableLooper.processAllMessages() + + val color = 0x23456789 + view.setStaticDrawableColor(color) + testableLooper.processAllMessages() + + assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) + + ViewUtils.detachView(view) + } + + private fun View.getGroupView(): View { + return this.requireViewById(R.id.mobile_group) + } + + private fun View.getIconView(): ImageView { + return this.requireViewById(R.id.mobile_signal) + } + + private fun View.getDotView(): View { + return this.requireViewById(R.id.status_bar_dot) + } +} + +private const val SLOT_NAME = "TestSlotName" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 043d55a73076..c960a06e6bb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants @@ -45,6 +46,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { private lateinit var qsIcon: QsMobileIconViewModel private lateinit var keyguardIcon: KeyguardMobileIconViewModel private lateinit var interactor: FakeMobileIconInteractor + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var tableLogBuffer: TableLogBuffer @@ -68,9 +70,9 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { commonImpl = MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope) - homeIcon = HomeMobileIconViewModel(commonImpl, logger) - qsIcon = QsMobileIconViewModel(commonImpl, logger) - keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger) + homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) + qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) + keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index d6cb76260f0b..58b50c7e7e6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy @@ -45,6 +46,7 @@ class MobileIconsViewModelTest : SysuiTestCase() { private lateinit var underTest: MobileIconsViewModel private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var constants: ConnectivityConstants @@ -67,6 +69,7 @@ class MobileIconsViewModelTest : SysuiTestCase() { logger, constants, testScope.backgroundScope, + statusBarPipelineFlags, ) interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt new file mode 100644 index 000000000000..3fe69837a761 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -0,0 +1,153 @@ +/* + * 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.systemui.statusbar.pipeline.shared.ui.view + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT +import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +class ModernStatusBarViewTest : SysuiTestCase() { + + private lateinit var binding: TestBinding + + @Test + fun initView_hasCorrectSlot() { + val view = ModernStatusBarView(context, null) + val binding = TestBinding() + + view.initView("slotName") { binding } + + assertThat(view.slot).isEqualTo("slotName") + } + + @Test + fun getVisibleState_icon_returnsIcon() { + val view = createAndInitView() + + view.setVisibleState(STATE_ICON, /* animate= */ false) + + assertThat(view.visibleState).isEqualTo(STATE_ICON) + } + + @Test + fun getVisibleState_dot_returnsDot() { + val view = createAndInitView() + + view.setVisibleState(STATE_DOT, /* animate= */ false) + + assertThat(view.visibleState).isEqualTo(STATE_DOT) + } + + @Test + fun getVisibleState_hidden_returnsHidden() { + val view = createAndInitView() + + view.setVisibleState(STATE_HIDDEN, /* animate= */ false) + + assertThat(view.visibleState).isEqualTo(STATE_HIDDEN) + } + + @Test + fun onDarkChanged_bindingReceivesIconAndDecorTint() { + val view = createAndInitView() + + view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678) + + assertThat(binding.iconTint).isEqualTo(0x12345678) + assertThat(binding.decorTint).isEqualTo(0x12345678) + } + + @Test + fun setStaticDrawableColor_bindingReceivesIconTint() { + val view = createAndInitView() + + view.setStaticDrawableColor(0x12345678) + + assertThat(binding.iconTint).isEqualTo(0x12345678) + } + + @Test + fun setDecorColor_bindingReceivesDecorColor() { + val view = createAndInitView() + + view.setDecorColor(0x23456789) + + assertThat(binding.decorTint).isEqualTo(0x23456789) + } + + @Test + fun isIconVisible_usesBinding_true() { + val view = createAndInitView() + + binding.shouldIconBeVisibleInternal = true + + assertThat(view.isIconVisible).isEqualTo(true) + } + + @Test + fun isIconVisible_usesBinding_false() { + val view = createAndInitView() + + binding.shouldIconBeVisibleInternal = false + + assertThat(view.isIconVisible).isEqualTo(false) + } + + private fun createAndInitView(): ModernStatusBarView { + val view = ModernStatusBarView(context, null) + binding = TestBinding() + view.initView(SLOT_NAME) { binding } + return view + } + + inner class TestBinding : ModernStatusBarViewBinding { + var iconTint: Int? = null + var decorTint: Int? = null + var onVisibilityStateChangedCalled: Boolean = false + + var shouldIconBeVisibleInternal: Boolean = true + + override fun onIconTintChanged(newTint: Int) { + iconTint = newTint + } + + override fun onDecorTintChanged(newTint: Int) { + decorTint = newTint + } + + override fun onVisibilityStateChanged(state: Int) { + onVisibilityStateChangedCalled = true + } + + override fun getShouldIconBeVisible(): Boolean { + return shouldIconBeVisibleInternal + } + } +} + +private const val SLOT_NAME = "TestSlotName" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index 59c10cd6df7c..b8ace2f04a61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view import android.content.res.ColorStateList -import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper @@ -27,7 +26,6 @@ import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.lifecycle.InstantTaskExecutorRule import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN @@ -52,8 +50,6 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import org.junit.Before -import org.junit.Ignore -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -70,7 +66,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var logger: ConnectivityPipelineLogger - @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock + private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var connectivityConstants: ConnectivityConstants @Mock @@ -83,9 +80,6 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { private lateinit var scope: CoroutineScope private lateinit var airplaneModeViewModel: AirplaneModeViewModel - @JvmField @Rule - val instantTaskExecutor = InstantTaskExecutorRule() - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -118,40 +112,6 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { ).home } - @Test - fun constructAndBind_hasCorrectSlot() { - val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel) - - assertThat(view.slot).isEqualTo("slotName") - } - - @Test - fun getVisibleState_icon_returnsIcon() { - val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) - - view.setVisibleState(STATE_ICON, /* animate= */ false) - - assertThat(view.visibleState).isEqualTo(STATE_ICON) - } - - @Test - fun getVisibleState_dot_returnsDot() { - val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) - - view.setVisibleState(STATE_DOT, /* animate= */ false) - - assertThat(view.visibleState).isEqualTo(STATE_DOT) - } - - @Test - fun getVisibleState_hidden_returnsHidden() { - val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) - - view.setVisibleState(STATE_HIDDEN, /* animate= */ false) - - assertThat(view.visibleState).isEqualTo(STATE_HIDDEN) - } - // Note: The following tests are more like integration tests, since they stand up a full // [WifiViewModel] and test the interactions between the view, view-binder, and view-model. @@ -235,24 +195,24 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { } @Test - @Ignore("b/262660044") fun onDarkChanged_iconHasNewColor() { - whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false) + whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) ViewUtils.attachView(view) testableLooper.processAllMessages() - val areas = ArrayList(listOf(Rect(0, 0, 1000, 1000))) val color = 0x12345678 - view.onDarkChanged(areas, 1.0f, color) + view.onDarkChanged(arrayListOf(), 1.0f, color) testableLooper.processAllMessages() assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) + + ViewUtils.detachView(view) } @Test fun setStaticDrawableColor_iconHasNewColor() { - whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false) + whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -262,6 +222,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { testableLooper.processAllMessages() assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) + + ViewUtils.detachView(view) } private fun View.getIconGroupView(): View { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d791c068c0f7..132d5ec52bf1 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -860,6 +860,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.stop(); } + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController.stop(); + } + if (mBrightnessSetting != null) { mBrightnessSetting.unregisterListener(mBrightnessSettingListener); } @@ -1104,6 +1108,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessEventRingBuffer = new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX); + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController.stop(); + } loadScreenOffBrightnessSensor(); int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux(); if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) { diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java index 6f50dac07b99..4d394c2546be 100644 --- a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java +++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java @@ -92,6 +92,10 @@ public class ScreenOffBrightnessSensorController implements SensorEventListener } } + void stop() { + setLightSensorEnabled(false); + } + float getAutomaticScreenBrightness() { if (mLastSensorValue < 0 || mLastSensorValue >= mSensorValueToLux.length || (!mRegistered diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c712167c4cd1..9215cabad25a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1456,8 +1456,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updatePictureInPictureMode(null, false); } else { mLastReportedMultiWindowMode = inMultiWindowMode; - ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS, - false /* ignoreVisibility */); + ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS); } } } @@ -3988,6 +3987,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void finishRelaunching() { + mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false); mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this); if (mPendingRelaunchCount > 0) { @@ -7745,13 +7745,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void setRequestedOrientation(int requestedOrientation) { + if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { + return; + } setOrientation(requestedOrientation, this); // Push the new configuration to the requested app in case where it's not pushed, e.g. when // the request is handled at task level with letterbox. if (!getMergedOverrideConfiguration().equals( mLastReportedConfiguration.getMergedConfiguration())) { - ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); + ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */, + false /* ignoreVisibility */, true /* isRequestedOrientationChanged */); } mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( @@ -9052,7 +9056,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) { return ensureActivityConfiguration(globalChanges, preserveWindow, - false /* ignoreVisibility */); + false /* ignoreVisibility */, false /* isRequestedOrientationChanged */); + } + + boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow, + boolean ignoreVisibility) { + return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility, + false /* isRequestedOrientationChanged */); } /** @@ -9066,11 +9076,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * (stopped state). This is useful for the case where we know the * activity will be visible soon and we want to ensure its configuration * before we make it visible. + * @param isRequestedOrientationChanged whether this is triggered in response to an app calling + * {@link android.app.Activity#setRequestedOrientation}. * @return False if the activity was relaunched and true if it wasn't relaunched because we * can't or the app handles the specific configuration that is changing. */ boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow, - boolean ignoreVisibility) { + boolean ignoreVisibility, boolean isRequestedOrientationChanged) { final Task rootTask = getRootTask(); if (rootTask.mConfigWillChange) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check " @@ -9194,6 +9206,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { mRelaunchReason = RELAUNCH_REASON_NONE; } + if (isRequestedOrientationChanged) { + mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true); + } if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not // do anything now, but just flag that it needs to be restarted when done pausing. diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 0bfc48b4b54c..e80c2607a0ad 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -98,7 +98,7 @@ class AppTaskImpl extends IAppTask.Stub { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } return mService.getRecentTasks().createRecentTaskInfo(task, - false /* stripExtras */, true /* getTasksAllowed */); + false /* stripExtras */); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 7266d2194779..ba0413df6325 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -287,7 +287,7 @@ final class DisplayRotationCompatPolicy { * <li>The activity has fixed orientation but not "locked" or "nosensor" one. * </ul> */ - private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) { + boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) { return activity != null && !activity.inMultiWindowMode() && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED // "locked" and "nosensor" values are often used by camera apps that can't diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 506396a932fe..f578fe0c111e 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -454,8 +454,7 @@ class InsetsPolicy { final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME); if (originalImeSource != null) { - final boolean imeVisibility = - w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME); + final boolean imeVisibility = w.getRequestedVisibility(ITYPE_IME); final InsetsState state = copyState ? new InsetsState(originalState) : originalState; final InsetsSource imeSource = new InsetsSource(originalImeSource); diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 9b8423327215..642732652ce9 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -222,6 +222,11 @@ final class LetterboxConfiguration { // See RefreshCallbackItem for context. private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true; + // Whether should ignore app requested orientation in response to an app + // calling Activity#setRequestedOrientation. See + // LetterboxUiController#shouldIgnoreRequestedOrientation for details. + private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled; + LetterboxConfiguration(Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext, () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext, @@ -274,10 +279,13 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsEnabledForTranslucentActivities); mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean( R.bool.config_isWindowManagerCameraCompatTreatmentEnabled); + mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean( + R.bool.config_isCompatFakeFocusEnabled); + mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); + mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); - mIsCompatFakeFocusEnabled = mContext.getResources() - .getBoolean(R.bool.config_isCompatFakeFocusEnabled); } /** @@ -1034,6 +1042,15 @@ final class LetterboxConfiguration { mIsCompatFakeFocusEnabled = enabled; } + /** + * Whether should ignore app requested orientation in response to an app calling + * {@link android.app.Activity#setRequestedOrientation}. See {@link + * LetterboxUiController#shouldIgnoreRequestedOrientation} for details. + */ + boolean isPolicyForIgnoringRequestedOrientationEnabled() { + return mIsPolicyForIgnoringRequestedOrientationEnabled; + } + /** Whether camera compatibility treatment is enabled. */ boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) { return mIsCameraCompatTreatmentEnabled diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index fd7e082beed4..0c8a6453e6fb 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,10 +17,13 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER; @@ -55,6 +58,8 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; +import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; @@ -74,6 +79,7 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.PrintWriter; +import java.util.function.BooleanSupplier; /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in @@ -131,12 +137,37 @@ final class LetterboxUiController { // DisplayRotationCompatPolicy. private boolean mIsRefreshAfterRotationRequested; + @Nullable + private final Boolean mBooleanPropertyIgnoreRequestedOrientation; + + private boolean mIsRelauchingAfterRequestedOrientationChanged; + LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { mLetterboxConfiguration = wmService.mLetterboxConfiguration; // Given activityRecord may not be fully constructed since LetterboxUiController // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; + + PackageManager packageManager = wmService.mContext.getPackageManager(); + mBooleanPropertyIgnoreRequestedOrientation = + readComponentProperty(packageManager, mActivityRecord.packageName, + mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, + PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + } + + @Nullable + private static Boolean readComponentProperty(PackageManager packageManager, String packageName, + BooleanSupplier gatingCondition, String propertyName) { + if (!gatingCondition.getAsBoolean()) { + return null; + } + try { + return packageManager.getProperty(propertyName, packageName).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + // No such property name. + } + return null; } /** Cleans up {@link Letterbox} if it exists.*/ @@ -154,6 +185,72 @@ final class LetterboxUiController { } /** + * Whether should ignore app requested orientation in response to an app + * calling {@link android.app.Activity#setRequestedOrientation}. + * + * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation} + * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has + * landscape natural orientation which app developers don't expect. For example, the loop can + * look like this: + * <ol> + * <li>App sets default orientation to "unspecified" at runtime + * <li>App requests to "portrait" after checking some condition (e.g. display rotation). + * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because + * app can't handle the corresponding config changes. + * <li>Loop goes back to (1) + * </ol> + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the treatment is enabled + * <li>Opt-out component property isn't enabled + * <li>Opt-in component property or per-app override are enabled + * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation} + * call from an app or camera compat force rotation treatment is active for the activity. + * </ul> + */ + boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { + if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) { + return false; + } + if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) { + return false; + } + if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation) + && !mActivityRecord.info.isChangeEnabled( + OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) { + return false; + } + if (mIsRelauchingAfterRequestedOrientationChanged) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " due to relaunching after setRequestedOrientation for " + mActivityRecord); + return true; + } + DisplayContent displayContent = mActivityRecord.mDisplayContent; + if (displayContent == null) { + return false; + } + if (displayContent.mDisplayRotationCompatPolicy != null + && displayContent.mDisplayRotationCompatPolicy + .isTreatmentEnabledForActivity(mActivityRecord)) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " due to camera compat treatment for " + mActivityRecord); + return true; + } + return false; + } + + /** + * Sets whether an activity is relaunching after the app has called {@link + * android.app.Activity#setRequestedOrientation}. + */ + void setRelauchingAfterRequestedOrientationChanged(boolean isRelaunching) { + mIsRelauchingAfterRequestedOrientationChanged = isRelaunching; + } + + /** * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked} * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}. */ diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 1fc061b2ca78..4860762a5f7f 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -976,7 +976,7 @@ class RecentTasks { continue; } - res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed)); + res.add(createRecentTaskInfo(task, true /* stripExtras */)); } return res; } @@ -1895,8 +1895,7 @@ class RecentTasks { /** * Creates a new RecentTaskInfo from a Task. */ - ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras, - boolean getTasksAllowed) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) { final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); // If the recent Task is detached, we consider it will be re-attached to the default // TaskDisplayArea because we currently only support recent overview in the default TDA. @@ -1908,9 +1907,6 @@ class RecentTasks { rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; rti.lastSnapshotData.set(tr.mLastTaskSnapshotData); - if (!getTasksAllowed) { - Task.trimIneffectiveInfo(tr, rti); - } // Fill in organized child task info for the task created by organizer. if (tr.mCreatedByOrganizer) { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 0e60274ba381..120fec0fe0e6 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -142,10 +142,6 @@ class RunningTasks { task.fillTaskInfo(rti, !mKeepIntentExtra); // Fill in some deprecated values rti.id = rti.taskId; - - if (!mAllowed) { - Task.trimIneffectiveInfo(task, rti); - } return rti; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ea6f2442a919..5806f7917bab 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3467,27 +3467,6 @@ class Task extends TaskFragment { info.isSleeping = shouldSleepActivities(); } - /** - * Removes the activity info if the activity belongs to a different uid, which is - * different from the app that hosts the task. - */ - static void trimIneffectiveInfo(Task task, TaskInfo info) { - final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing, - false /* traverseTopToBottom */); - final int baseActivityUid = - baseActivity != null ? baseActivity.getUid() : task.effectiveUid; - - if (info.topActivityInfo != null - && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) { - info.topActivity = null; - info.topActivityInfo = null; - } - - if (task.effectiveUid != baseActivityUid) { - info.baseActivity = null; - } - } - @Nullable PictureInPictureParams getPictureInPictureParams() { final Task topTask = getTopMostTask(); if (topTask == null) return null; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c874747b0b5a..c911b83a9a87 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -653,6 +653,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { t.setCornerRadius(targetLeash, 0); t.setShadowRadius(targetLeash, 0); t.setMatrix(targetLeash, 1, 0, 0, 1); + t.setAlpha(targetLeash, 1); // The bounds sent to the transition is always a real bounds. This means we lose // information about "null" bounds (inheriting from parent). Core will fix-up // non-organized window surface bounds; however, since Core can't touch organized diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index ca9ff6f15f3f..962a07ac6553 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -43,23 +43,23 @@ import static com.android.server.backup.testing.Utils.transferStreamedData; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.intThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.intThat; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadow.api.Shadow.extract; @@ -2185,7 +2185,7 @@ public class KeyValueBackupTaskTest { task.waitCancel(); reset(transportMock.transport); taskFinished.block(); - verifyZeroInteractions(transportMock.transport); + verifyNoInteractions(transportMock.transport); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java index ea04a193e569..5b10dc4e0bab 100644 --- a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java @@ -35,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.testutils.OffsettableClock; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,6 +74,15 @@ public class ScreenOffBrightnessSensorControllerTest { ); } + @After + public void tearDown() { + if (mController != null) { + // Stop the update Brightness loop. + mController.stop(); + mController = null; + } + } + @Test public void testBrightness() throws Exception { when(mSensorManager.registerListener(any(SensorEventListener.class), eq(mLightSensor), diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java new file mode 100644 index 000000000000..6d778afee88c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -0,0 +1,173 @@ +/* + * 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.server.wm; + +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; + +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.Property; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + + /** + * Test class for {@link LetterboxUiControllerTest}. + * + * Build/Install/Run: + * atest WmTests:LetterboxUiControllerTest + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class LetterboxUiControllerTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + private ActivityRecord mActivity; + private DisplayContent mDisplayContent; + private LetterboxUiController mController; + private LetterboxConfiguration mLetterboxConfiguration; + + @Before + public void setUp() throws Exception { + mActivity = setUpActivityWithComponent(); + + mLetterboxConfiguration = mWm.mLetterboxConfiguration; + spyOn(mLetterboxConfiguration); + + mController = new LetterboxUiController(mWm, mActivity); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() { + doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean()); + + // Recreate DisplayContent with DisplayRotationCompatPolicy + mActivity = setUpActivityWithComponent(); + mController = new LetterboxUiController(mWm, mActivity); + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + mController.setRelauchingAfterRequestedOrientationChanged(false); + + spyOn(mDisplayContent.mDisplayRotationCompatPolicy); + doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) + .isTreatmentEnabledForActivity(eq(mActivity)); + + assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() { + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ true); + mController = new LetterboxUiController(mWm, mActivity); + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + doReturn(false).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + + assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + private void mockThatProperty(String propertyName, boolean value) throws Exception { + Property property = new Property(propertyName, /* value */ value, /* packageName */ "", + /* className */ ""); + PackageManager pm = mWm.mContext.getPackageManager(); + spyOn(pm); + doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + } + + private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mController.setRelauchingAfterRequestedOrientationChanged(true); + } + + private ActivityRecord setUpActivityWithComponent() { + mDisplayContent = new TestDisplayContent + .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); + Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setOnTop(true) + .setTask(task) + // Set the component to be that of the test class in order to enable compat changes + .setComponent(ComponentName.createRelative(mContext, + com.android.server.wm.LetterboxUiControllerTest.class.getName())) + .build(); + return activity; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 0462e1be7a5f..adf694c2a88d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -30,7 +30,6 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.os.Process.NOBODY_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -1221,34 +1220,20 @@ public class RecentTasksTest extends WindowTestsBase { @Test public void testCreateRecentTaskInfo_detachedTask() { - final Task task = createTaskBuilder(".Task").build(); - new ActivityBuilder(mSupervisor.mService) - .setTask(task) - .setUid(NOBODY_UID) - .setComponent(getUniqueComponentName()) - .build(); + final Task task = createTaskBuilder(".Task").setCreateActivity(true).build(); final TaskDisplayArea tda = task.getDisplayArea(); assertTrue(task.isAttached()); assertTrue(task.supportsMultiWindow()); - RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true); assertTrue(info.supportsMultiWindow); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - false /* getTasksAllowed */); - - assertTrue(info.topActivity == null); - assertTrue(info.topActivityInfo == null); - assertTrue(info.baseActivity == null); - // The task can be put in split screen even if it is not attached now. task.removeImmediately(); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + info = mRecentTasks.createRecentTaskInfo(task, true); assertTrue(info.supportsMultiWindow); @@ -1257,8 +1242,7 @@ public class RecentTasksTest extends WindowTestsBase { doReturn(false).when(tda).supportsNonResizableMultiWindow(); doReturn(false).when(task).isResizeable(); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + info = mRecentTasks.createRecentTaskInfo(task, true); assertFalse(info.supportsMultiWindow); @@ -1266,8 +1250,7 @@ public class RecentTasksTest extends WindowTestsBase { // the device supports it. doReturn(true).when(tda).supportsNonResizableMultiWindow(); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + info = mRecentTasks.createRecentTaskInfo(task, true); assertTrue(info.supportsMultiWindow); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 650286a8b111..0568f2acf366 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -1032,6 +1032,9 @@ public class WindowStateTests extends WindowTestsBase { // Simulate app requests IME with updating all windows Insets State when IME is above app. mDisplayContent.setImeLayeringTarget(app); mDisplayContent.setImeInputTarget(app); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); + app.setRequestedVisibilities(requestedVisibilities); assertTrue(mDisplayContent.shouldImeAttachedToApp()); controller.getImeSourceProvider().scheduleShowImePostLayout(app); controller.getImeSourceProvider().getSource().setVisible(true); @@ -1069,6 +1072,9 @@ public class WindowStateTests extends WindowTestsBase { app2.mActivityRecord.mImeInsetsFrozenUntilStartInput = true; mDisplayContent.setImeLayeringTarget(app); mDisplayContent.setImeInputTarget(app); + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); + app.setRequestedVisibilities(requestedVisibilities); assertTrue(mDisplayContent.shouldImeAttachedToApp()); controller.getImeSourceProvider().scheduleShowImePostLayout(app); controller.getImeSourceProvider().getSource().setVisible(true); |