Merge "Stop the screen off brightness sensor controller" into tm-qpr-dev
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 7eacc3c..dea1a05 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -753,6 +753,19 @@
}
/**
+ * Temporary method for project b/197814683
+ * Starting from U, this will return true if the new wallpaper logic is enabled,
+ * i.e. if the lockscreen wallpaper always uses a wallpaperService and not a static image.
+ * In T, this is just a stub method that always return false.
+ *
+ * @return false
+ * @hide
+ */
+ public boolean isLockscreenLiveWallpaperEnabled() {
+ return false;
+ }
+
+ /**
* Indicate whether wcg (Wide Color Gamut) should be enabled.
* <p>
* Some devices lack of capability of mixed color spaces composition,
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 06399b9..9e5e8de 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1022,6 +1022,19 @@
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 2f77901..ed9cb00 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -814,6 +814,45 @@
}
/**
+ * 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 c54638a..3c143668 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -26,6 +26,11 @@
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 a8c2246..f7187c4 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 5c3e1d6..368ef96 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 13a2c78..d189ae2 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 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 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
index 4978e04..84ab448 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_title_color.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
rename to 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
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
rename to 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
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
rename to 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
similarity index 96%
rename from libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index 582a11c..8b4792a 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: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
similarity index 93%
rename from libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 51e634c..2a4cc02 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 b714d2e..39fe455 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 @@
// 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 b500f5f..b430157 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 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 467f374..9c2beb9 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 @@
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 @@
* 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 @@
*/
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 @@
*/
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 @@
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 @@
*/
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 @@
*/
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/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dd9ab98..d4746ee 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
@@ -560,7 +560,8 @@
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 f6914ef..23d6e34 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.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 096b2da..bfc5d0d 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 @@
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 fa4fef5..222b882 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 @@
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.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.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 1f2297b..fc2bf0a 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 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 95f7ef4..508dffc 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 @@
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 f28572f..cf07c6b 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.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 @@
dialog.show();
dialog.cancel();
- verifyZeroInteractions(successCallback);
+ verifyNoInteractions(successCallback);
verify(cancelCallback, times(1))
.run();
}
@@ -159,7 +159,7 @@
dialog.show();
dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick();
- verifyZeroInteractions(successCallback);
+ verifyNoInteractions(successCallback);
verify(cancelCallback, times(1))
.run();
}
@@ -180,7 +180,7 @@
verify(successCallback, times(1))
.accept("test", oldUserIcon);
- verifyZeroInteractions(cancelCallback);
+ verifyNoInteractions(cancelCallback);
}
@Test
@@ -198,7 +198,7 @@
verify(successCallback, times(1))
.accept("test", null);
- verifyZeroInteractions(cancelCallback);
+ verifyNoInteractions(cancelCallback);
}
@Test
@@ -219,7 +219,7 @@
verify(successCallback, times(1))
.accept(expectedNewName, mCurrentIcon);
- verifyZeroInteractions(cancelCallback);
+ verifyNoInteractions(cancelCallback);
}
@Test
@@ -238,7 +238,7 @@
verify(successCallback, times(1))
.accept("test", newPhoto);
- verifyZeroInteractions(cancelCallback);
+ verifyNoInteractions(cancelCallback);
}
@Test
@@ -257,7 +257,7 @@
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 0b3495d..ca0aa0d 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 @@
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/docs/modern-architecture.png b/packages/SystemUI/docs/modern-architecture.png
new file mode 100644
index 0000000..2636362
--- /dev/null
+++ b/packages/SystemUI/docs/modern-architecture.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-data-pipeline.md b/packages/SystemUI/docs/status-bar-data-pipeline.md
new file mode 100644
index 0000000..9fcdce1
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-data-pipeline.md
@@ -0,0 +1,261 @@
+# Status Bar Data Pipeline
+
+## Background
+
+The status bar is the UI shown at the top of the user's screen that gives them
+information about the time, notifications, and system status like mobile
+conectivity and battery level. This document is about the implementation of the
+wifi and mobile system icons on the right side:
+
+
+
+In Android U, the data pipeline that determines what mobile and wifi icons to
+show in the status bar has been re-written with a new architecture. This format
+generally follows Android best practices to
+[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
+This document serves as a guide for the new architecture, and as a guide for how
+OEMs can add customizations to the new architecture.
+
+## Architecture
+
+In the new architecture, there is a separate pipeline for each type of icon. For
+Android U, **only the wifi icon and mobile icons have been implemented in the
+new architecture**.
+
+As shown in the Android best practices guide, each new pipeline has a data
+layer, a domain layer, and a UI layer:
+
+
+
+The classes in the data layer are `repository` instances. The classes in the
+domain layer are `interactor` instances. The classes in the UI layer are
+`viewmodel` instances and `viewbinder` instances. In this document, "repository"
+and "data layer" will be used interchangably (and the same goes for the other
+layers).
+
+The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in
+`statusbar/pipeline/mobile`.
+
+#### Repository (data layer)
+
+System callbacks, broadcast receivers, configuration values are all defined
+here, and exposed through the appropriate interface. Where appropriate, we
+define `Model` objects at this layer so that clients do not have to rely on
+system-defined interfaces.
+
+#### Interactor (domain layer)
+
+Here is where we define the business logic and transform the data layer objects
+into something consumable by the ViewModel classes. For example,
+`MobileIconsInteractor` defines the CBRS filtering logic by exposing a
+`filteredSubscriptions` list.
+
+#### ViewModel (UI layer)
+
+View models should define the final piece of business logic mapping to UI logic.
+For example, the mobile view model checks the `IconInteractor.isRoaming` flow to
+decide whether or not to show the roaming indicator.
+
+#### ViewBinder
+
+These have already been implemented and configured. ViewBinders replace the old
+`applyMobileState` mechanism that existed in the `IconManager` classes of the
+old pipeline. A view binder associates a ViewModel with a View, and keeps the
+view up-to-date with the most recent information from the model.
+
+Any new fields added to the ViewModel classes need to be equivalently bound to
+the view here.
+
+### Putting it all together
+
+Putting that altogether, we have this overall architecture diagram for the
+icons:
+
+
+
+### Mobile icons architecture
+
+Because there can be multiple mobile connections at the same time, the mobile
+pipeline is split up hierarchically. At each level (data, domain, and UI), there
+is a singleton parent class that manages information relevant to **all** mobile
+connections, and multiple instances of child classes that manage information for
+a **single** mobile connection.
+
+For example, `MobileConnectionsRepository` is a singleton at the data layer that
+stores information relevant to **all** mobile connections, and it also manages a
+list of child `MobileConnectionRepository` classes. `MobileConnectionRepository`
+is **not** a singleton, and each individual `MobileConnectionRepository`
+instance fully qualifies the state of a **single** connection. This pattern is
+repeated at the `Interactor` and `ViewModel` layers for mobile.
+
+
+
+Note: Since there is at most one wifi connection, the wifi pipeline is not split
+up in the same way.
+
+## Customizations
+
+The new pipeline completely replaces these classes:
+
+* `WifiStatusTracker`
+* `MobileStatusTracker`
+* `NetworkSignalController` and `NetworkSignalControllerImpl`
+* `MobileSignalController`
+* `WifiSignalController`
+* `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and
+ `WifiIconState`)
+
+Any customizations in any of these classes will need to be migrated to the new
+pipeline. As a general rule, any change that would have gone into
+`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any
+change for `MobileSignalController` can be done in `MobileConnectionRepository`
+(see above on the relationship between those repositories).
+
+### Sample customization: New service
+
+Some customizations require listening to additional services to get additional
+data. This new architecture makes it easy to add additional services to the
+status bar data pipeline to get icon customizations.
+
+Below is a general guide to how a new service should be added. However, there
+may be exceptions to this guide for specific use cases.
+
+1. In the data layer (`repository` classes), add a new `StateFlow` that listens
+ to the service:
+
+ ```kotlin
+ class MobileConnectionsRepositoryImpl {
+ ...
+ val fooVal: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback = object : FooServiceCallback(), FooListener {
+ override fun onFooChanged(foo: Int) {
+ trySend(foo)
+ }
+ }
+
+ fooService.registerCallback(callback)
+
+ awaitClose { fooService.unregisterCallback(callback) }
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL)
+ }
+ ```
+
+1. In the domain layer (`interactor` classes), either use this new flow to
+ process values, or just expose the flow as-is for the UI layer.
+
+ For example, if `bar` should only be true when `foo` is positive:
+
+ ```kotlin
+ class MobileIconsInteractor {
+ ...
+ val bar: StateFlow<Boolean> =
+ mobileConnectionsRepo
+ .mapLatest { foo -> foo > 0 }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false)
+ }
+ ```
+
+1. In the UI layer (`viewmodel` classes), update the existing flows to process
+ the new value from the interactor.
+
+ For example, if the icon should be hidden when `bar` is true:
+
+ ```kotlin
+ class MobileIconViewModel {
+ ...
+ iconId: Flow<Int> = combine(
+ iconInteractor.level,
+ iconInteractor.numberOfLevels,
+ iconInteractor.bar,
+ ) { level, numberOfLevels, bar ->
+ if (bar) {
+ null
+ } else {
+ calcIcon(level, numberOfLevels)
+ }
+ }
+ ```
+
+## Demo mode
+
+SystemUI demo mode is a first-class citizen in the new pipeline. It is
+implemented as an entirely separate repository,
+`DemoMobileConnectionsRepository`. When the system moves into demo mode, the
+implementation of the data layer is switched to the demo repository via the
+`MobileRepositorySwitcher` class.
+
+Because the demo mode repositories implement the same interfaces as the
+production classes, any changes made above will have to be implemented for demo
+mode as well.
+
+1. Following from above, if `fooVal` is added to the
+ `MobileConnectionsRepository` interface:
+
+ ```kotlin
+ class DemoMobileConnectionsRepository {
+ private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE)
+ override val fooVal: StateFlow<Int> = _fooVal.asStateFlow()
+
+ // Process the state. **See below on how to add the command to the CLI**
+ fun processEnabledMobileState(state: Mobile) {
+ ...
+ _fooVal.value = state.fooVal
+ }
+ }
+ ```
+
+1. (Optional) If you want to enable the command line interface for setting and
+ testing this value in demo mode, you can add parsing logic to
+ `DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`:
+
+ ```kotlin
+ sealed interface FakeNetworkEventModel {
+ data class Mobile(
+ ...
+ // Add new fields here
+ val fooVal: Int?
+ )
+ }
+ ```
+
+ ```kotlin
+ class DemoModeMobileConnectionDataSource {
+ // Currently, the demo commands are implemented as an extension function on Bundle
+ private fun Bundle.activeMobileEvent(): Mobile {
+ ...
+ val fooVal = getString("fooVal")?.toInt()
+ return Mobile(
+ ...
+ fooVal = fooVal,
+ )
+ }
+ }
+ ```
+
+If step 2 is implemented, then you will be able to pass demo commands via the
+command line:
+
+```
+adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value>
+```
+
+## Migration plan
+
+For Android U, the new pipeline will be enabled and default. However, the old
+pipeline code will still be around just in case the new pipeline doesn’t do well
+in the testing phase.
+
+For Android V, the old pipeline will be completely removed and the new pipeline
+will be the one source of truth.
+
+Our ask for OEMs is to default to using the new pipeline in Android U. If there
+are customizations that seem difficult to migrate over to the new pipeline,
+please file a bug with us and we’d be more than happy to consult on the best
+solution. The new pipeline was designed with customizability in mind, so our
+hope is that working the new pipeline will be easier and faster.
+
+Note: The new pipeline currently only supports the wifi and mobile icons. The
+other system status bar icons may be migrated to a similar architecture in the
+future.
diff --git a/packages/SystemUI/docs/status-bar-mobile-pipeline.png b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
new file mode 100644
index 0000000..620563d
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-pipeline.png b/packages/SystemUI/docs/status-bar-pipeline.png
new file mode 100644
index 0000000..1c568c9
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar.png b/packages/SystemUI/docs/status-bar.png
new file mode 100644
index 0000000..3a5af0e
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar.png
Binary files differ
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f0cc42e..7d72598 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 8cd734c..7c4fa6c 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 @@
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 4d914fe..15fed32 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 @@
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 ab442b5..3e81c7c 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 @@
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.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 @@
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 @@
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 @@
// 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 e86fee2..ed9a188 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 @@
.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 b0dc41f..24cd930 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 @@
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 @@
*/
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 b9318b1..24370d2 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 @@
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 @@
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 @@
)
.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 @@
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 @@
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 0000000..f67876b
--- /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 0000000..cc0ec54
--- /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 cc67c84..2aff12c 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.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 @@
@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 @@
}
}
- 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 be7782c..7a73486 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 @@
*/
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 @@
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 a4615cc..02c3a65 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 @@
/** 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 0000000..a2c1209
--- /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 043d55a..c960a06 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 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 @@
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 @@
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 d6cb762..58b50c7 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 @@
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 @@
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 @@
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 0000000..3fe6983
--- /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 59c10cd..b8ace2f 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 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 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 @@
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 @@
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 @@
).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 @@
}
@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 @@
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/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 5bdfa70..9278743 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2644,6 +2644,9 @@
}
}
+ /**
+ * Uniquely identifies a Sensor, with the combination of Type and Name.
+ */
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 6331a5d..aafba5a 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -50,7 +50,6 @@
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
@@ -68,6 +67,7 @@
import com.android.server.LocalServices;
import com.android.server.display.utils.AmbientFilter;
import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.SensorUtils;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -520,14 +520,20 @@
}
/**
- * A utility to make this class aware of the new display configs whenever the default display is
- * changed
+ * Called when the underlying display device of the default display is changed.
+ * Some data in this class relates to the physical display of the device, and so we need to
+ * reload the configurations based on this.
+ * E.g. the brightness sensors and refresh rate capabilities depend on the physical display
+ * device that is being used, so will be reloaded.
+ *
+ * @param displayDeviceConfig configurations relating to the underlying display device.
*/
public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
mSettingsObserver.setRefreshRates(displayDeviceConfig,
/* attemptLoadingFromDeviceConfig= */ true);
mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
/* attemptLoadingFromDeviceConfig= */ true);
+ mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
}
/**
@@ -1541,6 +1547,9 @@
private SensorManager mSensorManager;
private Sensor mLightSensor;
+ private Sensor mRegisteredLightSensor;
+ private String mLightSensorType;
+ private String mLightSensorName;
private final LightSensorEventListener mLightSensorListener =
new LightSensorEventListener();
// Take it as low brightness before valid sensor data comes
@@ -1701,17 +1710,8 @@
return mLowAmbientBrightnessThresholds;
}
- public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
- mSensorManager = sensorManager;
- mLightSensor = lightSensor;
-
- mSensorManager.registerListener(mLightSensorListener,
- mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
- }
-
public void observe(SensorManager sensorManager) {
mSensorManager = sensorManager;
- final ContentResolver cr = mContext.getContentResolver();
mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
// DeviceConfig is accessible after system ready.
@@ -1855,6 +1855,10 @@
pw.println(" mAmbientHighBrightnessThresholds: " + d);
}
+ pw.println(" mLightSensor: " + mLightSensor);
+ pw.println(" mRegisteredLightSensor: " + mRegisteredLightSensor);
+ pw.println(" mLightSensorName: " + mLightSensorName);
+ pw.println(" mLightSensorType: " + mLightSensorType);
mLightSensorListener.dumpLocked(pw);
if (mAmbientFilter != null) {
@@ -1908,27 +1912,9 @@
}
if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
- Resources resources = mContext.getResources();
- String lightSensorType = resources.getString(
- com.android.internal.R.string.config_displayLightSensorType);
+ Sensor lightSensor = getLightSensor();
- Sensor lightSensor = null;
- if (!TextUtils.isEmpty(lightSensorType)) {
- List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
- for (int i = 0; i < sensors.size(); i++) {
- Sensor sensor = sensors.get(i);
- if (lightSensorType.equals(sensor.getStringType())) {
- lightSensor = sensor;
- break;
- }
- }
- }
-
- if (lightSensor == null) {
- lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
- }
-
- if (lightSensor != null) {
+ if (lightSensor != null && lightSensor != mLightSensor) {
final Resources res = mContext.getResources();
mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
@@ -1938,15 +1924,40 @@
mAmbientFilter = null;
mLightSensor = null;
}
-
+ updateSensorStatus();
if (mRefreshRateChangeable) {
- updateSensorStatus();
synchronized (mLock) {
onBrightnessChangedLocked();
}
}
}
+ private void reloadLightSensor(DisplayDeviceConfig displayDeviceConfig) {
+ reloadLightSensorData(displayDeviceConfig);
+ restartObserver();
+ }
+
+ private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
+ // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
+ // it naturally falls back to the global config.xml.
+ if (displayDeviceConfig != null
+ && displayDeviceConfig.getAmbientLightSensor() != null) {
+ // This covers both the ddc and the config.xml fallback
+ mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
+ mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
+ } else if (mLightSensorName == null && mLightSensorType == null) {
+ Resources resources = mContext.getResources();
+ mLightSensorType = resources.getString(
+ com.android.internal.R.string.config_displayLightSensorType);
+ mLightSensorName = "";
+ }
+ }
+
+ private Sensor getLightSensor() {
+ return SensorUtils.findSensor(mSensorManager, mLightSensorType,
+ mLightSensorName, Sensor.TYPE_LIGHT);
+ }
+
/**
* Checks to see if at least one value is positive, in which case it is necessary to listen
* to value changes.
@@ -2088,17 +2099,36 @@
if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
&& isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
- mSensorManager.registerListener(mLightSensorListener,
- mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
- if (mLoggingEnabled) {
- Slog.d(TAG, "updateSensorStatus: registerListener");
- }
+ registerLightSensor();
+
} else {
- mLightSensorListener.removeCallbacks();
- mSensorManager.unregisterListener(mLightSensorListener);
- if (mLoggingEnabled) {
- Slog.d(TAG, "updateSensorStatus: unregisterListener");
- }
+ unregisterSensorListener();
+ }
+ }
+
+ private void registerLightSensor() {
+ if (mRegisteredLightSensor == mLightSensor) {
+ return;
+ }
+
+ if (mRegisteredLightSensor != null) {
+ unregisterSensorListener();
+ }
+
+ mSensorManager.registerListener(mLightSensorListener,
+ mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+ mRegisteredLightSensor = mLightSensor;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateSensorStatus: registerListener");
+ }
+ }
+
+ private void unregisterSensorListener() {
+ mLightSensorListener.removeCallbacks();
+ mSensorManager.unregisterListener(mLightSensorListener);
+ mRegisteredLightSensor = null;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateSensorStatus: unregisterListener");
}
}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 4924ad5..48bc46c 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -33,6 +33,10 @@
*/
public static Sensor findSensor(SensorManager sensorManager, String sensorType,
String sensorName, int fallbackType) {
+ if (sensorManager == null) {
+ return null;
+ }
+
if ("".equals(sensorName) && "".equals(sensorType)) {
return null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c712167..9215cab 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 @@
updatePictureInPictureMode(null, false);
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- false /* ignoreVisibility */);
+ ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
}
}
}
@@ -3988,6 +3987,7 @@
}
void finishRelaunching() {
+ mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
if (mPendingRelaunchCount > 0) {
@@ -7745,13 +7745,17 @@
}
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 @@
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 @@
* (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 @@
} 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 0bfc48b..e80c260 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 @@
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 7266d21..ba0413d 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 @@
* <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/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 9b84233..6427326 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 @@
// 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 @@
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 @@
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 fd7e082..0c8a645 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 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.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 @@
// 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 @@
}
/**
+ * 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 1fc061b..4860762 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 @@
continue;
}
- res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
+ res.add(createRecentTaskInfo(task, true /* stripExtras */));
}
return res;
}
@@ -1895,8 +1895,7 @@
/**
* 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 @@
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 0e60274..120fec0 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 @@
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 ea6f244..5806f79 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 @@
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 c874747..c911b83 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 @@
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 ca9ff6f..962a07a 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.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 @@
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/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index cfea63b..b133a2a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -40,6 +40,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -74,6 +75,7 @@
import android.test.mock.MockContentResolver;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.Display;
import androidx.test.core.app.ApplicationProvider;
@@ -102,6 +104,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1939,6 +1942,61 @@
new int[]{20});
}
+ @Test
+ public void testSensorReloadOnDeviceSwitch() throws Exception {
+ // First, configure brightness zones or DMD won't register for sensor data.
+ final FakeDeviceConfig config = mInjector.getDeviceConfig();
+ config.setRefreshRateInHighZone(60);
+ config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+ config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+ setPeakRefreshRate(90 /*fps*/);
+ director.getSettingsObserver().setDefaultRefreshRate(90);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
+ when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
+ director.start(sensorManager);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+ .registerListener(
+ listenerCaptor.capture(),
+ eq(lightSensorOne),
+ anyInt(),
+ any(Handler.class));
+
+ DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+ when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
+ when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+ when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+ when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+ when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+ when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+
+ Resources resMock = mock(Resources.class);
+ when(resMock.getInteger(
+ com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+ .thenReturn(3);
+ ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+ doAnswer((Answer<Void>) invocation -> {
+ valueArgumentCaptor.getValue().type = 4;
+ valueArgumentCaptor.getValue().data = 13;
+ return null;
+ }).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
+ when(mContext.getResources()).thenReturn(resMock);
+
+ director.defaultDisplayDeviceUpdated(ddcMock);
+
+ verify(sensorManager).unregisterListener(any(SensorEventListener.class));
+ verify(sensorManager).registerListener(any(SensorEventListener.class),
+ eq(lightSensorTwo), anyInt(), any(Handler.class));
+ }
+
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
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 0000000..6d778afe
--- /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 0462e1b..adf694c 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.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 @@
@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 @@
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 @@
// 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);
}