Merge "Remove unused paramater to finish" into udc-qpr-dev
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index 8f5c2a0..a753f96 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,3 +1,7 @@
# Bug component: 175220
+aprasath@google.com
+kumarashishg@google.com
+sarup@google.com
+anothermark@google.com
badhri@google.com
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 10336bd..561b5a6 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -70,10 +70,6 @@
public static final Flag OTP_REDACTION =
devFlag("persist.sysui.notification.otp_redaction");
- /** Gating the removal of sorting-notifications-by-interruptiveness. */
- public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
- releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
-
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c120af3..f795bd7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1780,10 +1780,6 @@
<string name="biometric_dialog_default_title">Verify it\u2019s you</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
<string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
- <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
- <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
- <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
- <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
<string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 08c404b..3610ead 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2572,8 +2572,6 @@
<java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_dialog_default_subtitle" />
- <java-symbol type="string" name="biometric_dialog_face_subtitle" />
- <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
<java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index ee6996d..2c10065 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -661,14 +661,26 @@
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
!= LAYOUT_DIRECTION_RTL;
- final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
- R.dimen.bubble_stack_starting_offset_y);
- // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
- return new BubbleStackView.RelativeStackPosition(
- startOnLeft,
- startingVerticalOffset / mPositionRect.height())
- .getAbsolutePositionInRegion(getAllowableStackPositionRegion(
- 1 /* default starts with 1 bubble */));
+ final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
+ 1 /* default starts with 1 bubble */);
+ if (isLargeScreen()) {
+ // We want the stack to be visually centered on the edge, so we need to base it
+ // of a rect that includes insets.
+ final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
+ final float offset = desiredY / mScreenRect.height();
+ return new BubbleStackView.RelativeStackPosition(
+ startOnLeft,
+ offset)
+ .getAbsolutePositionInRegion(allowableStackPositionRegion);
+ } else {
+ final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.bubble_stack_starting_offset_y);
+ // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
+ return new BubbleStackView.RelativeStackPosition(
+ startOnLeft,
+ startingVerticalOffset / mPositionRect.height())
+ .getAbsolutePositionInRegion(allowableStackPositionRegion);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
new file mode 100644
index 0000000..fd000ee
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PointF
+import android.util.Size
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipDisplayLayoutState
+
+class LegacySizeSpecSource(
+ private val context: Context,
+ private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+
+ private var mDefaultMinSize = 0
+ /** The absolute minimum an overridden size's edge can be */
+ private var mOverridableMinSize = 0
+ /** The preferred minimum (and default minimum) size specified by apps. */
+ private var mOverrideMinSize: Size? = null
+
+ private var mDefaultSizePercent = 0f
+ private var mMinimumSizePercent = 0f
+ private var mMaxAspectRatioForMinSize = 0f
+ private var mMinAspectRatioForMinSize = 0f
+
+ init {
+ reloadResources()
+ }
+
+ private fun reloadResources() {
+ val res: Resources = context.getResources()
+
+ mDefaultMinSize = res.getDimensionPixelSize(
+ R.dimen.default_minimal_size_pip_resizable_task)
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task)
+
+ mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent)
+ mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1)
+
+ mMaxAspectRatioForMinSize = res.getFloat(
+ R.dimen.config_pictureInPictureAspectRatioLimitForMinSize)
+ mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize
+ }
+
+ override fun onConfigurationChanged() {
+ reloadResources()
+ }
+
+ override fun getMaxSize(aspectRatio: Float): Size {
+ val insetBounds = pipDisplayLayoutState.insetBounds
+
+ val shorterLength: Int = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height())
+ val totalHorizontalPadding: Int = (insetBounds.left +
+ (getDisplayBounds().width() - insetBounds.right))
+ val totalVerticalPadding: Int = (insetBounds.top +
+ (getDisplayBounds().height() - insetBounds.bottom))
+
+ return if (aspectRatio > 1f) {
+ val maxWidth = Math.max(getDefaultSize(aspectRatio).width,
+ shorterLength - totalHorizontalPadding)
+ val maxHeight = (maxWidth / aspectRatio).toInt()
+ Size(maxWidth, maxHeight)
+ } else {
+ val maxHeight = Math.max(getDefaultSize(aspectRatio).height,
+ shorterLength - totalVerticalPadding)
+ val maxWidth = (maxHeight * aspectRatio).toInt()
+ Size(maxWidth, maxHeight)
+ }
+ }
+
+ override fun getDefaultSize(aspectRatio: Float): Size {
+ if (mOverrideMinSize != null) {
+ return getMinSize(aspectRatio)
+ }
+ val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height())
+ val minSize = Math.max(getMinEdgeSize().toFloat(),
+ smallestDisplaySize * mDefaultSizePercent).toInt()
+ val width: Int
+ val height: Int
+ if (aspectRatio <= mMinAspectRatioForMinSize ||
+ aspectRatio > mMaxAspectRatioForMinSize) {
+ // Beyond these points, we can just use the min size as the shorter edge
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size
+ width = minSize
+ height = Math.round(width / aspectRatio)
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize
+ width = Math.round(height * aspectRatio)
+ }
+ } else {
+ // Within these points, ensure that the bounds fit within the radius of the limits
+ // at the points
+ val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize
+ val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat())
+ height = Math.round(Math.sqrt((radius * radius /
+ (aspectRatio * aspectRatio + 1)).toDouble())).toInt()
+ width = Math.round(height * aspectRatio)
+ }
+ return Size(width, height)
+ }
+
+ override fun getMinSize(aspectRatio: Float): Size {
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+ }
+ val shorterLength: Int = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height())
+ val minWidth: Int
+ val minHeight: Int
+ if (aspectRatio > 1f) {
+ minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(),
+ shorterLength * mMinimumSizePercent).toInt()
+ minHeight = (minWidth / aspectRatio).toInt()
+ } else {
+ minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(),
+ shorterLength * mMinimumSizePercent).toInt()
+ minWidth = (minHeight * aspectRatio).toInt()
+ }
+ return Size(minWidth, minHeight)
+ }
+
+ override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+ val smallestSize = Math.min(size.width, size.height)
+ val minSize = Math.max(getMinEdgeSize(), smallestSize)
+ val width: Int
+ val height: Int
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size.
+ width = minSize
+ height = Math.round(width / aspectRatio)
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize
+ width = Math.round(height * aspectRatio)
+ }
+ return Size(width, height)
+ }
+
+ private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds
+
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ override fun setOverrideMinSize(overrideMinSize: Size?) {
+ mOverrideMinSize = overrideMinSize
+ }
+
+ /** Returns the preferred minimal size specified by the activity in PIP. */
+ override fun getOverrideMinSize(): Size? {
+ val overrideMinSize = mOverrideMinSize ?: return null
+ return if (overrideMinSize.width < mOverridableMinSize ||
+ overrideMinSize.height < mOverridableMinSize) {
+ Size(mOverridableMinSize, mOverridableMinSize)
+ } else {
+ overrideMinSize
+ }
+ }
+
+ private fun getMinEdgeSize(): Int {
+ return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+ }
+
+ /**
+ * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+ *
+ *
+ * Overridden min size needs to be adjusted in its own way while making sure that the target
+ * aspect ratio is maintained
+ *
+ * @param aspectRatio target aspect ratio
+ */
+ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+ val size = getOverrideMinSize() ?: return null
+ val sizeAspectRatio = size.width / size.height.toFloat()
+ return if (sizeAspectRatio > aspectRatio) {
+ // Size is wider, fix the width and increase the height
+ Size(size.width, (size.width / aspectRatio).toInt())
+ } else {
+ // Size is taller, fix the height and adjust the width.
+ Size((size.height * aspectRatio).toInt(), size.height)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
new file mode 100644
index 0000000..c563068
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipDisplayLayoutState
+import java.io.PrintWriter
+
+class PhoneSizeSpecSource(
+ private val context: Context,
+ private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+ private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16
+
+ private var mDefaultMinSize = 0
+ /** The absolute minimum an overridden size's edge can be */
+ private var mOverridableMinSize = 0
+ /** The preferred minimum (and default minimum) size specified by apps. */
+ private var mOverrideMinSize: Size? = null
+
+
+ /** Default and minimum percentages for the PIP size logic. */
+ private val mDefaultSizePercent: Float
+ private val mMinimumSizePercent: Float
+
+ /** Aspect ratio that the PIP size spec logic optimizes for. */
+ private var mOptimizedAspectRatio = 0f
+
+ init {
+ mDefaultSizePercent = SystemProperties
+ .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
+ mMinimumSizePercent = SystemProperties
+ .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
+
+ reloadResources()
+ }
+
+ private fun reloadResources() {
+ val res: Resources = context.getResources()
+
+ mDefaultMinSize = res.getDimensionPixelSize(
+ R.dimen.default_minimal_size_pip_resizable_task)
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task)
+
+ val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
+ // make sure the optimized aspect ratio is valid with a default value to fall back to
+ mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
+ DEFAULT_OPTIMIZED_ASPECT_RATIO
+ } else {
+ requestedOptAspRatio
+ }
+ }
+
+ override fun onConfigurationChanged() {
+ reloadResources()
+ }
+
+ /**
+ * Calculates the max size of PIP.
+ *
+ * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+ * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+ * whole screen. A linear function is used to calculate these sizes.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the max size of the PIP
+ */
+ override fun getMaxSize(aspectRatio: Float): Size {
+ val insetBounds = pipDisplayLayoutState.insetBounds
+ val displayBounds = pipDisplayLayoutState.displayBounds
+
+ val totalHorizontalPadding: Int = (insetBounds.left +
+ (displayBounds.width() - insetBounds.right))
+ val totalVerticalPadding: Int = (insetBounds.top +
+ (displayBounds.height() - insetBounds.bottom))
+ val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding,
+ displayBounds.height() - totalVerticalPadding)
+ var maxWidth: Int
+ val maxHeight: Int
+
+ // use the optimized max sizing logic only within a certain aspect ratio range
+ if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+ // this formula and its derivation is explained in b/198643358#comment16
+ maxWidth = Math.round(mOptimizedAspectRatio * shorterLength +
+ shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio))
+ // make sure the max width doesn't go beyond shorter screen length after rounding
+ maxWidth = Math.min(maxWidth, shorterLength)
+ maxHeight = Math.round(maxWidth / aspectRatio)
+ } else {
+ if (aspectRatio > 1f) {
+ maxWidth = shorterLength
+ maxHeight = Math.round(maxWidth / aspectRatio)
+ } else {
+ maxHeight = shorterLength
+ maxWidth = Math.round(maxHeight * aspectRatio)
+ }
+ }
+ return Size(maxWidth, maxHeight)
+ }
+
+ /**
+ * Decreases the dimensions by a percentage relative to max size to get default size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the default size of the PIP
+ */
+ override fun getDefaultSize(aspectRatio: Float): Size {
+ val minSize = getMinSize(aspectRatio)
+ if (mOverrideMinSize != null) {
+ return minSize
+ }
+ val maxSize = getMaxSize(aspectRatio)
+ val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
+ minSize.width)
+ val defaultHeight = Math.round(defaultWidth / aspectRatio)
+ return Size(defaultWidth, defaultHeight)
+ }
+
+ /**
+ * Decreases the dimensions by a certain percentage relative to max size to get min size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the min size of the PIP
+ */
+ override fun getMinSize(aspectRatio: Float): Size {
+ // if there is an overridden min size provided, return that
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+ }
+ val maxSize = getMaxSize(aspectRatio)
+ var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
+ var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
+
+ // make sure the calculated min size is not smaller than the allowed default min size
+ if (aspectRatio > 1f) {
+ minHeight = Math.max(minHeight, mDefaultMinSize)
+ minWidth = Math.round(minHeight * aspectRatio)
+ } else {
+ minWidth = Math.max(minWidth, mDefaultMinSize)
+ minHeight = Math.round(minWidth / aspectRatio)
+ }
+ return Size(minWidth, minHeight)
+ }
+
+ /**
+ * Returns the size for target aspect ratio making sure new size conforms with the rules.
+ *
+ *
+ * Recalculates the dimensions such that the target aspect ratio is achieved, while
+ * maintaining the same maximum size to current size ratio.
+ *
+ * @param size current size
+ * @param aspectRatio target aspect ratio
+ */
+ override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+ if (size == mOverrideMinSize) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+ }
+
+ val currAspectRatio = size.width.toFloat() / size.height
+
+ // getting the percentage of the max size that current size takes
+ val currentMaxSize = getMaxSize(currAspectRatio)
+ val currentPercent = size.width.toFloat() / currentMaxSize.width
+
+ // getting the max size for the target aspect ratio
+ val updatedMaxSize = getMaxSize(aspectRatio)
+ var width = Math.round(updatedMaxSize.width * currentPercent)
+ var height = Math.round(updatedMaxSize.height * currentPercent)
+
+ // adjust the dimensions if below allowed min edge size
+ val minEdgeSize =
+ if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+
+ if (width < minEdgeSize && aspectRatio <= 1) {
+ width = minEdgeSize
+ height = Math.round(width / aspectRatio)
+ } else if (height < minEdgeSize && aspectRatio > 1) {
+ height = minEdgeSize
+ width = Math.round(height * aspectRatio)
+ }
+
+ // reduce the dimensions of the updated size to the calculated percentage
+ return Size(width, height)
+ }
+
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ override fun setOverrideMinSize(overrideMinSize: Size?) {
+ mOverrideMinSize = overrideMinSize
+ }
+
+ /** Returns the preferred minimal size specified by the activity in PIP. */
+ override fun getOverrideMinSize(): Size? {
+ val overrideMinSize = mOverrideMinSize ?: return null
+ return if (overrideMinSize.width < mOverridableMinSize ||
+ overrideMinSize.height < mOverridableMinSize) {
+ Size(mOverridableMinSize, mOverridableMinSize)
+ } else {
+ overrideMinSize
+ }
+ }
+
+ /**
+ * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+ *
+ *
+ * Overridden min size needs to be adjusted in its own way while making sure that the target
+ * aspect ratio is maintained
+ *
+ * @param aspectRatio target aspect ratio
+ */
+ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+ val size = getOverrideMinSize() ?: return null
+ val sizeAspectRatio = size.width / size.height.toFloat()
+ return if (sizeAspectRatio > aspectRatio) {
+ // Size is wider, fix the width and increase the height
+ Size(size.width, (size.width / aspectRatio).toInt())
+ } else {
+ // Size is taller, fix the height and adjust the width.
+ Size((size.height * aspectRatio).toInt(), size.height)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
+ pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
+ pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
+ pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
+ pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
+ pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
new file mode 100644
index 0000000..7b3b9ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.util.Size
+import java.io.PrintWriter
+
+interface SizeSpecSource {
+ /** Returns max size allowed for the PIP window */
+ fun getMaxSize(aspectRatio: Float): Size
+
+ /** Returns default size for the PIP window */
+ fun getDefaultSize(aspectRatio: Float): Size
+
+ /** Returns min size allowed for the PIP window */
+ fun getMinSize(aspectRatio: Float): Size
+
+ /** Returns the adjusted size based on current size and target aspect ratio */
+ fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size
+
+ /** Overrides the minimum pip size requested by the app */
+ fun setOverrideMinSize(overrideMinSize: Size?)
+
+ /** Returns the minimum pip size requested by the app */
+ fun getOverrideMinSize(): Size?
+
+ /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
+ fun getOverrideMinEdgeSize(): Int {
+ val overrideMinSize = getOverrideMinSize() ?: return 0
+ return Math.min(overrideMinSize.width, overrideMinSize.height)
+ }
+
+ fun onConfigurationChanged() {}
+
+ /** Dumps the internal state of the size spec */
+ fun dump(pw: PrintWriter, prefix: String) {}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 16c3960..54be901 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -30,6 +30,8 @@
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -53,7 +55,6 @@
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -87,7 +88,6 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -110,8 +110,7 @@
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
- pipDisplayLayoutState,
+ pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
@@ -123,8 +122,8 @@
@WMSingleton
@Provides
static PipBoundsState providePipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
+ SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
}
@WMSingleton
@@ -141,19 +140,12 @@
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
- PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipSizeSpecHandler(context, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
- PipSizeSpecHandler pipSizeSpecHandler) {
+ PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
- pipKeepClearAlgorithm, pipSizeSpecHandler);
+ pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
}
// Handler is used by Icon.loadDrawableAsync
@@ -177,14 +169,14 @@
PhonePipMenuController menuPhoneController,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ SizeSpecSource sizeSpecSource,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
+ pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@@ -243,6 +235,13 @@
@WMSingleton
@Provides
+ static SizeSpecSource provideSizeSpecSource(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+ }
+
+ @WMSingleton
+ @Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 360bf8b..52c6d20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -28,6 +28,8 @@
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.Pip;
@@ -42,7 +44,6 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -138,23 +139,23 @@
@Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PipSizeSpecHandler pipSizeSpecHandler) {
+ PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
- pipSizeSpecHandler);
+ pipDisplayLayoutState, sizeSpecSource);
}
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
- return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
+ SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ static SizeSpecSource provideSizeSpecSource(Context context,
PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipSizeSpecHandler(context, pipDisplayLayoutState);
+ return new LegacySizeSpecSource(context, pipDisplayLayoutState);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index f51eb52..ac711ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -28,7 +28,7 @@
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import java.io.PrintWriter;
@@ -41,7 +41,8 @@
private static final float INVALID_SNAP_FRACTION = -1f;
@NonNull private final PipBoundsState mPipBoundsState;
- @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
+ @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
+ @NonNull protected final SizeSpecSource mSizeSpecSource;
private final PipSnapAlgorithm mSnapAlgorithm;
private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
@@ -53,11 +54,13 @@
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
@NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ @NonNull SizeSpecSource sizeSpecSource) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mSizeSpecSource = sizeSpecSource;
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -74,11 +77,6 @@
R.dimen.config_pictureInPictureDefaultAspectRatio);
mDefaultStackGravity = res.getInteger(
R.integer.config_defaultPictureInPictureGravity);
- final String screenEdgeInsetsDpString = res.getString(
- R.string.config_defaultPictureInPictureScreenEdgeInsets);
- final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
- ? Size.parseSize(screenEdgeInsetsDpString)
- : null;
mMinAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
@@ -160,8 +158,8 @@
// If either dimension is smaller than the allowed minimum, adjust them
// according to mOverridableMinSize
return new Size(
- Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
- Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
+ Math.max(windowLayout.minWidth, getOverrideMinEdgeSize()),
+ Math.max(windowLayout.minHeight, getOverrideMinEdgeSize()));
}
return null;
}
@@ -255,10 +253,10 @@
final Size size;
if (useCurrentMinEdgeSize || useCurrentSize) {
// Use the existing size but adjusted to the new aspect ratio.
- size = mPipSizeSpecHandler.getSizeForAspectRatio(
+ size = mSizeSpecSource.getSizeForAspectRatio(
new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
} else {
- size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+ size = mSizeSpecSource.getDefaultSize(aspectRatio);
}
final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -287,7 +285,7 @@
getInsetBounds(insetBounds);
// Calculate the default size
- defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
+ defaultSize = mSizeSpecSource.getDefaultSize(mDefaultAspectRatio);
// Now that we have the default size, apply the snap fraction if valid or position the
// bounds using the default gravity.
@@ -309,7 +307,11 @@
* Populates the bounds on the screen that the PIP can be visible in.
*/
public void getInsetBounds(Rect outRect) {
- outRect.set(mPipSizeSpecHandler.getInsetBounds());
+ outRect.set(mPipDisplayLayoutState.getInsetBounds());
+ }
+
+ private int getOverrideMinEdgeSize() {
+ return mSizeSpecSource.getOverrideMinEdgeSize();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 9a775df..279ffc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -36,7 +36,7 @@
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -87,7 +87,7 @@
private int mStashOffset;
private @Nullable PipReentryState mPipReentryState;
private final LauncherState mLauncherState = new LauncherState();
- private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
+ private final @NonNull SizeSpecSource mSizeSpecSource;
private @Nullable ComponentName mLastPipComponentName;
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
@@ -127,17 +127,20 @@
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mSizeSpecSource = sizeSpecSource;
mPipDisplayLayoutState = pipDisplayLayoutState;
}
/** Reloads the resources. */
public void onConfigurationChanged() {
reloadResources();
+
+ // update the size spec resources upon config change too
+ mSizeSpecSource.onConfigurationChanged();
}
private void reloadResources() {
@@ -319,7 +322,7 @@
/** Sets the preferred size of PIP as specified by the activity in PIP mode. */
public void setOverrideMinSize(@Nullable Size overrideMinSize) {
final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
- mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
+ mSizeSpecSource.setOverrideMinSize(overrideMinSize);
if (changed && mOnMinimalSizeChangeCallback != null) {
mOnMinimalSizeChangeCallback.run();
}
@@ -328,12 +331,12 @@
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
- return mPipSizeSpecHandler.getOverrideMinSize();
+ return mSizeSpecSource.getOverrideMinSize();
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
- return mPipSizeSpecHandler.getOverrideMinEdgeSize();
+ return mSizeSpecSource.getOverrideMinEdgeSize();
}
/** Get the state of the bounds in motion. */
@@ -613,5 +616,6 @@
}
mLauncherState.dump(pw, innerPrefix);
mMotionBoundsState.dump(pw, innerPrefix);
+ mSizeSpecSource.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
index 0f76af4..456f85b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -16,12 +16,18 @@
package com.android.wm.shell.pip;
+import static com.android.wm.shell.pip.PipUtils.dpToPx;
+
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.dagger.WMSingleton;
@@ -40,13 +46,51 @@
private int mDisplayId;
@NonNull private DisplayLayout mDisplayLayout;
+ private Point mScreenEdgeInsets = null;
+
@Inject
public PipDisplayLayoutState(Context context) {
mContext = context;
mDisplayLayout = new DisplayLayout();
+ reloadResources();
}
- /** Update the display layout. */
+ /** Responds to configuration change. */
+ public void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ private void reloadResources() {
+ Resources res = mContext.getResources();
+
+ final String screenEdgeInsetsDpString = res.getString(
+ R.string.config_defaultPictureInPictureScreenEdgeInsets);
+ final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+ ? Size.parseSize(screenEdgeInsetsDpString)
+ : null;
+ mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+ : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+ dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+ }
+
+ public Point getScreenEdgeInsets() {
+ return mScreenEdgeInsets;
+ }
+
+ /**
+ * Returns the inset bounds the PIP window can be visible in.
+ */
+ public Rect getInsetBounds() {
+ Rect insetBounds = new Rect();
+ Rect insets = getDisplayLayout().stableInsets();
+ insetBounds.set(insets.left + getScreenEdgeInsets().x,
+ insets.top + getScreenEdgeInsets().y,
+ getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x,
+ getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y);
+ return insetBounds;
+ }
+
+ /** Set the display layout. */
public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
}
@@ -87,5 +131,6 @@
pw.println(prefix + TAG);
pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+ pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 26b7b68..f396f3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -142,7 +142,6 @@
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
private PipDisplayLayoutState mPipDisplayLayoutState;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
@@ -406,7 +405,6 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -430,7 +428,7 @@
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
@@ -448,7 +446,6 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -474,7 +471,6 @@
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
@@ -711,7 +707,7 @@
// Try to move the PiP window if we have entered PiP mode.
if (mPipTransitionState.hasEnteredPip()) {
final Rect pipBounds = mPipBoundsState.getBounds();
- final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+ final Point edgeInsets = mPipDisplayLayoutState.getScreenEdgeInsets();
if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
// PiP bounds is too big to fit either half, bail early.
return;
@@ -770,7 +766,7 @@
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
- mPipSizeSpecHandler.onConfigurationChanged();
+ mPipDisplayLayoutState.onConfigurationChanged();
}
@Override
@@ -1224,7 +1220,6 @@
mPipTaskOrganizer.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
- mPipSizeSpecHandler.dump(pw, innerPrefix);
mPipDisplayLayoutState.dump(pw, innerPrefix);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
deleted file mode 100644
index c6e5cf2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ /dev/null
@@ -1,536 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import static com.android.wm.shell.pip.PipUtils.dpToPx;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.SystemProperties;
-import android.util.Size;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-
-import java.io.PrintWriter;
-
-/**
- * Acts as a source of truth for appropriate size spec for PIP.
- */
-public class PipSizeSpecHandler {
- private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
-
- @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
-
- private final SizeSpecSource mSizeSpecSourceImpl;
-
- /** The preferred minimum (and default minimum) size specified by apps. */
- @Nullable private Size mOverrideMinSize;
- private int mOverridableMinSize;
-
- /** Used to store values obtained from resource files. */
- private Point mScreenEdgeInsets;
- private float mMinAspectRatioForMinSize;
- private float mMaxAspectRatioForMinSize;
- private int mDefaultMinSize;
-
- @NonNull private final Context mContext;
-
- private interface SizeSpecSource {
- /** Returns max size allowed for the PIP window */
- Size getMaxSize(float aspectRatio);
-
- /** Returns default size for the PIP window */
- Size getDefaultSize(float aspectRatio);
-
- /** Returns min size allowed for the PIP window */
- Size getMinSize(float aspectRatio);
-
- /** Returns the adjusted size based on current size and target aspect ratio */
- Size getSizeForAspectRatio(Size size, float aspectRatio);
-
- /** Updates internal resources on configuration changes */
- default void reloadResources() {}
- }
-
- /**
- * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
- */
- private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
- private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
-
- /** Default and minimum percentages for the PIP size logic. */
- private final float mDefaultSizePercent;
- private final float mMinimumSizePercent;
-
- /** Aspect ratio that the PIP size spec logic optimizes for. */
- private float mOptimizedAspectRatio;
-
- private SizeSpecLargeScreenOptimizedImpl() {
- mDefaultSizePercent = Float.parseFloat(SystemProperties
- .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
- mMinimumSizePercent = Float.parseFloat(SystemProperties
- .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
- }
-
- @Override
- public void reloadResources() {
- final Resources res = mContext.getResources();
-
- mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
- // make sure the optimized aspect ratio is valid with a default value to fall back to
- if (mOptimizedAspectRatio > 1) {
- mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
- }
- }
-
- /**
- * Calculates the max size of PIP.
- *
- * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
- * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
- * whole screen. A linear function is used to calculate these sizes.
- *
- * @param aspectRatio aspect ratio of the PIP window
- * @return dimensions of the max size of the PIP
- */
- @Override
- public Size getMaxSize(float aspectRatio) {
- final int totalHorizontalPadding = getInsetBounds().left
- + (getDisplayBounds().width() - getInsetBounds().right);
- final int totalVerticalPadding = getInsetBounds().top
- + (getDisplayBounds().height() - getInsetBounds().bottom);
-
- final int shorterLength = Math.min(getDisplayBounds().width() - totalHorizontalPadding,
- getDisplayBounds().height() - totalVerticalPadding);
-
- int maxWidth, maxHeight;
-
- // use the optimized max sizing logic only within a certain aspect ratio range
- if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
- // this formula and its derivation is explained in b/198643358#comment16
- maxWidth = Math.round(mOptimizedAspectRatio * shorterLength
- + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
- + aspectRatio));
- // make sure the max width doesn't go beyond shorter screen length after rounding
- maxWidth = Math.min(maxWidth, shorterLength);
- maxHeight = Math.round(maxWidth / aspectRatio);
- } else {
- if (aspectRatio > 1f) {
- maxWidth = shorterLength;
- maxHeight = Math.round(maxWidth / aspectRatio);
- } else {
- maxHeight = shorterLength;
- maxWidth = Math.round(maxHeight * aspectRatio);
- }
- }
-
- return new Size(maxWidth, maxHeight);
- }
-
- /**
- * Decreases the dimensions by a percentage relative to max size to get default size.
- *
- * @param aspectRatio aspect ratio of the PIP window
- * @return dimensions of the default size of the PIP
- */
- @Override
- public Size getDefaultSize(float aspectRatio) {
- Size minSize = this.getMinSize(aspectRatio);
-
- if (mOverrideMinSize != null) {
- return minSize;
- }
-
- Size maxSize = this.getMaxSize(aspectRatio);
-
- int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
- minSize.getWidth());
- int defaultHeight = Math.round(defaultWidth / aspectRatio);
-
- return new Size(defaultWidth, defaultHeight);
- }
-
- /**
- * Decreases the dimensions by a certain percentage relative to max size to get min size.
- *
- * @param aspectRatio aspect ratio of the PIP window
- * @return dimensions of the min size of the PIP
- */
- @Override
- public Size getMinSize(float aspectRatio) {
- // if there is an overridden min size provided, return that
- if (mOverrideMinSize != null) {
- return adjustOverrideMinSizeToAspectRatio(aspectRatio);
- }
-
- Size maxSize = this.getMaxSize(aspectRatio);
-
- int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
- int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
-
- // make sure the calculated min size is not smaller than the allowed default min size
- if (aspectRatio > 1f) {
- minHeight = Math.max(minHeight, mDefaultMinSize);
- minWidth = Math.round(minHeight * aspectRatio);
- } else {
- minWidth = Math.max(minWidth, mDefaultMinSize);
- minHeight = Math.round(minWidth / aspectRatio);
- }
- return new Size(minWidth, minHeight);
- }
-
- /**
- * Returns the size for target aspect ratio making sure new size conforms with the rules.
- *
- * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
- * maintaining the same maximum size to current size ratio.
- *
- * @param size current size
- * @param aspectRatio target aspect ratio
- */
- @Override
- public Size getSizeForAspectRatio(Size size, float aspectRatio) {
- float currAspectRatio = (float) size.getWidth() / size.getHeight();
-
- // getting the percentage of the max size that current size takes
- Size currentMaxSize = getMaxSize(currAspectRatio);
- float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
-
- // getting the max size for the target aspect ratio
- Size updatedMaxSize = getMaxSize(aspectRatio);
-
- int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
- int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
-
- // adjust the dimensions if below allowed min edge size
- if (width < getMinEdgeSize() && aspectRatio <= 1) {
- width = getMinEdgeSize();
- height = Math.round(width / aspectRatio);
- } else if (height < getMinEdgeSize() && aspectRatio > 1) {
- height = getMinEdgeSize();
- width = Math.round(height * aspectRatio);
- }
-
- // reduce the dimensions of the updated size to the calculated percentage
- return new Size(width, height);
- }
- }
-
- private class SizeSpecDefaultImpl implements SizeSpecSource {
- private float mDefaultSizePercent;
- private float mMinimumSizePercent;
-
- @Override
- public void reloadResources() {
- final Resources res = mContext.getResources();
-
- mMaxAspectRatioForMinSize = res.getFloat(
- R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
- mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
-
- mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
- mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
- }
-
- @Override
- public Size getMaxSize(float aspectRatio) {
- final int shorterLength = Math.min(getDisplayBounds().width(),
- getDisplayBounds().height());
-
- final int totalHorizontalPadding = getInsetBounds().left
- + (getDisplayBounds().width() - getInsetBounds().right);
- final int totalVerticalPadding = getInsetBounds().top
- + (getDisplayBounds().height() - getInsetBounds().bottom);
-
- final int maxWidth, maxHeight;
-
- if (aspectRatio > 1f) {
- maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
- shorterLength - totalHorizontalPadding);
- maxHeight = (int) (maxWidth / aspectRatio);
- } else {
- maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
- shorterLength - totalVerticalPadding);
- maxWidth = (int) (maxHeight * aspectRatio);
- }
-
- return new Size(maxWidth, maxHeight);
- }
-
- @Override
- public Size getDefaultSize(float aspectRatio) {
- if (mOverrideMinSize != null) {
- return this.getMinSize(aspectRatio);
- }
-
- final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
- getDisplayBounds().height());
- final int minSize = (int) Math.max(getMinEdgeSize(),
- smallestDisplaySize * mDefaultSizePercent);
-
- final int width;
- final int height;
-
- if (aspectRatio <= mMinAspectRatioForMinSize
- || aspectRatio > mMaxAspectRatioForMinSize) {
- // Beyond these points, we can just use the min size as the shorter edge
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- } else {
- // Within these points, ensure that the bounds fit within the radius of the limits
- // at the points
- final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
- final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
- height = (int) Math.round(Math.sqrt((radius * radius)
- / (aspectRatio * aspectRatio + 1)));
- width = Math.round(height * aspectRatio);
- }
-
- return new Size(width, height);
- }
-
- @Override
- public Size getMinSize(float aspectRatio) {
- if (mOverrideMinSize != null) {
- return adjustOverrideMinSizeToAspectRatio(aspectRatio);
- }
-
- final int shorterLength = Math.min(getDisplayBounds().width(),
- getDisplayBounds().height());
- final int minWidth, minHeight;
-
- if (aspectRatio > 1f) {
- minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
- shorterLength * mMinimumSizePercent);
- minHeight = (int) (minWidth / aspectRatio);
- } else {
- minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
- shorterLength * mMinimumSizePercent);
- minWidth = (int) (minHeight * aspectRatio);
- }
-
- return new Size(minWidth, minHeight);
- }
-
- @Override
- public Size getSizeForAspectRatio(Size size, float aspectRatio) {
- final int smallestSize = Math.min(size.getWidth(), size.getHeight());
- final int minSize = Math.max(getMinEdgeSize(), smallestSize);
-
- final int width;
- final int height;
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size.
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
-
- return new Size(width, height);
- }
- }
-
- public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
- mContext = context;
- mPipDisplayLayoutState = pipDisplayLayoutState;
-
- // choose between two implementations of size spec logic
- if (supportsPipSizeLargeScreen()) {
- mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
- } else {
- mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
- }
-
- reloadResources();
- }
-
- /** Reloads the resources */
- public void onConfigurationChanged() {
- reloadResources();
- }
-
- private void reloadResources() {
- final Resources res = mContext.getResources();
-
- mDefaultMinSize = res.getDimensionPixelSize(
- R.dimen.default_minimal_size_pip_resizable_task);
- mOverridableMinSize = res.getDimensionPixelSize(
- R.dimen.overridable_minimal_size_pip_resizable_task);
-
- final String screenEdgeInsetsDpString = res.getString(
- R.string.config_defaultPictureInPictureScreenEdgeInsets);
- final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
- ? Size.parseSize(screenEdgeInsetsDpString)
- : null;
- mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
- : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
- dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
-
- // update the internal resources of the size spec source's stub
- mSizeSpecSourceImpl.reloadResources();
- }
-
- @NonNull
- private Rect getDisplayBounds() {
- return mPipDisplayLayoutState.getDisplayBounds();
- }
-
- public Point getScreenEdgeInsets() {
- return mScreenEdgeInsets;
- }
-
- /**
- * Returns the inset bounds the PIP window can be visible in.
- */
- public Rect getInsetBounds() {
- Rect insetBounds = new Rect();
- DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
- Rect insets = displayLayout.stableInsets();
- insetBounds.set(insets.left + mScreenEdgeInsets.x,
- insets.top + mScreenEdgeInsets.y,
- displayLayout.width() - insets.right - mScreenEdgeInsets.x,
- displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
- return insetBounds;
- }
-
- /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
- public void setOverrideMinSize(@Nullable Size overrideMinSize) {
- mOverrideMinSize = overrideMinSize;
- }
-
- /** Returns the preferred minimal size specified by the activity in PIP. */
- @Nullable
- public Size getOverrideMinSize() {
- if (mOverrideMinSize != null
- && (mOverrideMinSize.getWidth() < mOverridableMinSize
- || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
- return new Size(mOverridableMinSize, mOverridableMinSize);
- }
-
- return mOverrideMinSize;
- }
-
- /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
- public int getOverrideMinEdgeSize() {
- if (mOverrideMinSize == null) return 0;
- return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
- }
-
- public int getMinEdgeSize() {
- return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
- }
-
- /**
- * Returns the size for the max size spec.
- */
- public Size getMaxSize(float aspectRatio) {
- return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
- }
-
- /**
- * Returns the size for the default size spec.
- */
- public Size getDefaultSize(float aspectRatio) {
- return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
- }
-
- /**
- * Returns the size for the min size spec.
- */
- public Size getMinSize(float aspectRatio) {
- return mSizeSpecSourceImpl.getMinSize(aspectRatio);
- }
-
- /**
- * Returns the adjusted size so that it conforms to the given aspectRatio.
- *
- * @param size current size
- * @param aspectRatio target aspect ratio
- */
- public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
- if (size.equals(mOverrideMinSize)) {
- return adjustOverrideMinSizeToAspectRatio(aspectRatio);
- }
-
- return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
- }
-
- /**
- * Returns the adjusted overridden min size if it is set; otherwise, returns null.
- *
- * <p>Overridden min size needs to be adjusted in its own way while making sure that the target
- * aspect ratio is maintained
- *
- * @param aspectRatio target aspect ratio
- */
- @Nullable
- @VisibleForTesting
- Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
- if (mOverrideMinSize == null) {
- return null;
- }
- final Size size = getOverrideMinSize();
- final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
- if (sizeAspectRatio > aspectRatio) {
- // Size is wider, fix the width and increase the height
- return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
- } else {
- // Size is taller, fix the height and adjust the width.
- return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
- }
- }
-
- @VisibleForTesting
- boolean supportsPipSizeLargeScreen() {
- // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
- // can be injected
- return SystemProperties
- .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
- }
-
- private boolean isTv() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
- }
-
- /** Dumps internal state. */
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
- pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
- pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 415f398..ab65c9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -85,7 +86,7 @@
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@NonNull private final PipBoundsState mPipBoundsState;
- @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
+ @NonNull private final SizeSpecSource mSizeSpecSource;
private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer;
@@ -179,7 +180,7 @@
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull SizeSpecSource sizeSpecSource,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -190,7 +191,7 @@
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mSizeSpecSource = sizeSpecSource;
mPipTaskOrganizer = pipTaskOrganizer;
mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger;
@@ -413,7 +414,7 @@
// Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
- Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+ Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
Rect expandedMovementBounds = new Rect();
@@ -517,10 +518,10 @@
private void updatePinchResizeSizeConstraints(float aspectRatio) {
final int minWidth, minHeight, maxWidth, maxHeight;
- minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
- minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
- maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
- maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
+ minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
+ minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
+ maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
+ maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 825b969..cd58ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,10 +36,11 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -62,9 +63,10 @@
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ @NonNull SizeSpecSource sizeSpecSource) {
super(context, tvPipBoundsState, pipSnapAlgorithm,
- new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
+ new PipKeepClearAlgorithmInterface() {}, pipDisplayLayoutState, sizeSpecSource);
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
@@ -291,7 +293,7 @@
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxHeight = displayLayout.height()
- - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
+ - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().y)
- pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
@@ -311,7 +313,7 @@
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxWidth = displayLayout.width()
- - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
+ - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().x)
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index e1737ec..4757efc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@
import android.view.Gravity;
import android.view.View;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,9 +76,9 @@
private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull SizeSpecSource sizeSpecSource,
@NonNull PipDisplayLayoutState pipDisplayLayoutState) {
- super(context, pipSizeSpecHandler, pipDisplayLayoutState);
+ super(context, sizeSpecSource, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
mPreviousCollapsedGravity = mDefaultGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 69609ac..3758b68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2720,7 +2720,8 @@
== TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
// Open to side should only be used when split already active and foregorund.
if (mainChild == null && sideChild == null) {
- Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+ "Launched a task in split, but didn't receive any task in transition."));
// This should happen when the target app is already on front, so just cancel.
mSplitTransitions.mPendingEnter.cancel(null);
return true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index 5f72397..139724f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -209,9 +209,19 @@
* {@link BubbleStackView.RelativeStackPosition}.
*/
private float getDefaultYPosition() {
- final float desiredY = mContext.getResources().getDimensionPixelOffset(
+ final boolean isTablet = mPositioner.isLargeScreen();
+
+ // On tablet the position is centered, on phone it is an offset from the top.
+ final float desiredY = isTablet
+ ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
+ : mContext.getResources().getDimensionPixelOffset(
R.dimen.bubble_stack_starting_offset_y);
- float offsetPercent = desiredY / mPositioner.getAvailableRect().height();
+ // Since we're visually centering the bubbles on tablet, use total screen height rather
+ // than the available height.
+ final float height = isTablet
+ ? mPositioner.getScreenRect().height()
+ : mPositioner.getAvailableRect().height();
+ float offsetPercent = desiredY / height;
offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
final RectF allowableStackRegion =
mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index addc233..bf1b7f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,7 +32,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
@@ -60,7 +61,8 @@
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
- private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipBoundsState mPipBoundsState;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
@@ -68,11 +70,12 @@
public void setUp() throws Exception {
initializeMockResources();
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
- mPipSizeSpecHandler);
+ mPipDisplayLayoutState, mSizeSpecSource);
DisplayLayout layout =
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
@@ -132,7 +135,7 @@
@Test
public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
- final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(DEFAULT_ASPECT_RATIO);
mPipBoundsState.setOverrideMinSize(null);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index f320004..4341c4c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -35,7 +35,8 @@
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
@@ -58,6 +59,7 @@
private static final int OVERRIDABLE_MIN_SIZE = 40;
private PipBoundsState mPipBoundsState;
+ private SizeSpecSource mSizeSpecSource;
private ComponentName mTestComponentName1;
private ComponentName mTestComponentName2;
@@ -69,8 +71,8 @@
OVERRIDABLE_MIN_SIZE);
PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipBoundsState = new PipBoundsState(mContext,
- new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, pipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, pipDisplayLayoutState);
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 842c699..1e3fe42 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -52,8 +52,9 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
@@ -87,7 +88,7 @@
private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
private ComponentName mComponent1;
@@ -99,12 +100,12 @@
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
- mPipSizeSpecHandler);
+ mPipDisplayLayoutState, mSizeSpecSource);
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
similarity index 84%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 528c23c..024cba3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -32,6 +32,8 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipDisplayLayoutState;
import org.junit.After;
@@ -47,10 +49,10 @@
import java.util.function.Function;
/**
- * Unit test against {@link PipSizeSpecHandler} with feature flag on.
+ * Unit test against {@link PhoneSizeSpecSource}
*/
@RunWith(AndroidTestingRunner.class)
-public class PipSizeSpecHandlerTest extends ShellTestCase {
+public class PhoneSizeSpecSourceTest extends ShellTestCase {
/** A sample overridden min edge size. */
private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
/** A sample default min edge size */
@@ -75,7 +77,7 @@
@Mock private Resources mResources;
private PipDisplayLayoutState mPipDisplayLayoutState;
- private TestPipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
/**
* Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -158,10 +160,10 @@
mPipDisplayLayoutState.setDisplayLayout(displayLayout);
setUpStaticSystemPropertiesSession();
- mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
// no overridden min edge size by default
- mPipSizeSpecHandler.setOverrideMinSize(null);
+ mSizeSpecSource.setOverrideMinSize(null);
}
@After
@@ -172,19 +174,19 @@
@Test
public void testGetMaxSize() {
forEveryTestCaseCheck(sExpectedMaxSizes,
- (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
+ (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
}
@Test
public void testGetDefaultSize() {
forEveryTestCaseCheck(sExpectedDefaultSizes,
- (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
+ (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
}
@Test
public void testGetMinSize() {
forEveryTestCaseCheck(sExpectedMinSizes,
- (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
+ (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
}
@Test
@@ -193,7 +195,7 @@
Size initSize = new Size(600, 337);
Size expectedSize = new Size(338, 601);
- Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+ Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
Assert.assertEquals(expectedSize, actualSize);
}
@@ -201,26 +203,12 @@
@Test
public void testGetSizeForAspectRatio_withOverrideMinSize() {
// an initial size with a 1:1 aspect ratio
- mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
- OVERRIDE_MIN_EDGE_SIZE));
- // make sure initial size is same as override min size
- Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
+ Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE);
+ mSizeSpecSource.setOverrideMinSize(initSize);
Size expectedSize = new Size(40, 71);
- Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+ Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
Assert.assertEquals(expectedSize, actualSize);
}
-
- static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
-
- TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
- super(context, displayLayoutState);
- }
-
- @Override
- boolean supportsPipSizeLargeScreen() {
- return true;
- }
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 85167cb..2cc28ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -109,7 +109,6 @@
@Mock private PipMotionHelper mMockPipMotionHelper;
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
- @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@@ -134,7 +133,7 @@
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipBoundsState, mMockPipDisplayLayoutState,
mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -226,7 +225,7 @@
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipBoundsState, mMockPipDisplayLayoutState,
mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 1dfdbf6..689b5c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -36,6 +36,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -87,7 +89,7 @@
private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
@@ -97,13 +99,14 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
- mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
+ mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
+ mSizeSpecSource);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 10b1ddf..852183c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,6 +33,8 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -92,7 +94,7 @@
private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
private DisplayLayout mDisplayLayout;
@@ -108,16 +110,16 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
- new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
+ new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
- mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+ mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer,
pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
// We aren't actually using ShellInit, so just call init directly
mPipTouchHandler.onInit();
@@ -162,8 +164,8 @@
// getting the expected min and max size
float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
- Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
- Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+ Size expectedMinSize = mSizeSpecSource.getMinSize(aspectRatio);
+ Size expectedMaxSize = mSizeSpecSource.getMaxSize(aspectRatio);
assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
verify(mPipResizeGestureHandler, times(1))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index f9b7723..da6951b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,9 +26,10 @@
import android.view.Gravity;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before;
import org.junit.Test;
@@ -47,7 +48,7 @@
private TvPipBoundsState mTvPipBoundsState;
private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
@@ -57,11 +58,11 @@
}
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+ mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
+ mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource,
mPipDisplayLayoutState);
mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
- mMockPipSnapAlgorithm, mPipSizeSpecHandler);
+ mMockPipSnapAlgorithm, mPipDisplayLayoutState, mSizeSpecSource);
setRTL(false);
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4759689..e8c9d0d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6929,6 +6929,114 @@
/**
* @hide
+ * Describes an audio device that has not been categorized with a specific
+ * audio type.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * @hide
+ * Describes an audio device which is categorized as something different.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_OTHER = 1;
+
+ /**
+ * @hide
+ * Describes an audio device which was categorized as speakers.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_SPEAKER = 2;
+
+ /**
+ * @hide
+ * Describes an audio device which was categorized as headphones.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_HEADPHONES = 3;
+
+ /**
+ * @hide
+ * Describes an audio device which was categorized as car-kit.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_CARKIT = 4;
+
+ /**
+ * @hide
+ * Describes an audio device which was categorized as watch.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_WATCH = 5;
+
+ /**
+ * @hide
+ * Describes an audio device which was categorized as hearing aid.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_HEARING_AID = 6;
+
+ /**
+ * @hide
+ * Describes an audio device which was categorized as receiver.
+ */
+ public static final int AUDIO_DEVICE_CATEGORY_RECEIVER = 7;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = "AUDIO_DEVICE_CATEGORY", value = {
+ AUDIO_DEVICE_CATEGORY_UNKNOWN,
+ AUDIO_DEVICE_CATEGORY_OTHER,
+ AUDIO_DEVICE_CATEGORY_SPEAKER,
+ AUDIO_DEVICE_CATEGORY_HEADPHONES,
+ AUDIO_DEVICE_CATEGORY_CARKIT,
+ AUDIO_DEVICE_CATEGORY_WATCH,
+ AUDIO_DEVICE_CATEGORY_HEARING_AID,
+ AUDIO_DEVICE_CATEGORY_RECEIVER }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioDeviceCategory {}
+
+ /** @hide */
+ public static String audioDeviceCategoryToString(int audioDeviceCategory) {
+ switch (audioDeviceCategory) {
+ case AUDIO_DEVICE_CATEGORY_UNKNOWN: return "AUDIO_DEVICE_CATEGORY_UNKNOWN";
+ case AUDIO_DEVICE_CATEGORY_OTHER: return "AUDIO_DEVICE_CATEGORY_OTHER";
+ case AUDIO_DEVICE_CATEGORY_SPEAKER: return "AUDIO_DEVICE_CATEGORY_SPEAKER";
+ case AUDIO_DEVICE_CATEGORY_HEADPHONES: return "AUDIO_DEVICE_CATEGORY_HEADPHONES";
+ case AUDIO_DEVICE_CATEGORY_CARKIT: return "AUDIO_DEVICE_CATEGORY_CARKIT";
+ case AUDIO_DEVICE_CATEGORY_WATCH: return "AUDIO_DEVICE_CATEGORY_WATCH";
+ case AUDIO_DEVICE_CATEGORY_HEARING_AID: return "AUDIO_DEVICE_CATEGORY_HEARING_AID";
+ case AUDIO_DEVICE_CATEGORY_RECEIVER: return "AUDIO_DEVICE_CATEGORY_RECEIVER";
+ default:
+ return new StringBuilder("unknown AudioDeviceCategory ").append(
+ audioDeviceCategory).toString();
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the audio device type of a Bluetooth device given its MAC address
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
+ @AudioDeviceCategory int btAudioDeviceType) {
+ try {
+ getService().setBluetoothAudioDeviceCategory(address, isBle, btAudioDeviceType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Gets the audio device type of a Bluetooth device given its MAC address
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @AudioDeviceCategory
+ public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) {
+ try {
+ return getService().getBluetoothAudioDeviceCategory(address, isBle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Sound dose warning at every 100% of dose during integration window
*/
public static final int CSD_WARNING_DOSE_REACHED_1X = 1;
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7ce189b..180c7fd 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -322,6 +322,12 @@
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
boolean isCsdEnabled();
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ int getBluetoothAudioDeviceCategory(in String address, boolean isBle);
+
int setHdmiSystemAudioSupported(boolean on);
boolean isHdmiSystemAudioSupported();
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 29e8716..cda919f 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -442,6 +442,7 @@
* but it must be released if your activity or service is being destroyed.
*/
public void release() {
+ setCallback(null);
try {
mBinder.destroySession();
} catch (RemoteException e) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 441d3a5..a6536a8c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -29,6 +29,7 @@
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -365,16 +366,17 @@
public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
- final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+ // Should iterate through the cloned set to avoid ConcurrentModificationException
+ final Set<CachedBluetoothDevice> memberDevices = new HashSet<>(device.getMemberDevice());
if (!memberDevices.isEmpty()) {
- // Main device is unpaired, to unpair the member device
+ // Main device is unpaired, also unpair the member devices
for (CachedBluetoothDevice memberDevice : memberDevices) {
memberDevice.unpair();
memberDevice.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
device.removeMemberDevice(memberDevice);
}
} else if (mainDevice != null) {
- // the member device unpaired, to unpair main device
+ // Member device is unpaired, also unpair the main device
mainDevice.unpair();
}
mainDevice = mHearingAidDeviceManager.findMainDevice(device);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 23b6308..f60f8db 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3227,6 +3227,15 @@
return settingsState.getSettingLocked(name);
}
+ private static boolean shouldExcludeSettingFromReset(Setting setting, String prefix) {
+ // If a prefix was specified, exclude settings whose names don't start with it.
+ if (prefix != null && !setting.getName().startsWith(prefix)) {
+ return true;
+ }
+ // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty.
+ return Global.SECURE_FRP_MODE.equals(setting.getName());
+ }
+
public void resetSettingsLocked(int type, int userId, String packageName, int mode,
String tag) {
resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
@@ -3249,7 +3258,7 @@
Setting setting = settingsState.getSettingLocked(name);
if (packageName.equals(setting.getPackageName())) {
if ((tag != null && !tag.equals(setting.getTag()))
- || (prefix != null && !setting.getName().startsWith(prefix))) {
+ || shouldExcludeSettingFromReset(setting, prefix)) {
continue;
}
if (settingsState.resetSettingLocked(name)) {
@@ -3270,7 +3279,7 @@
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
- if (prefix != null && !setting.getName().startsWith(prefix)) {
+ if (shouldExcludeSettingFromReset(setting, prefix)) {
continue;
}
if (settingsState.resetSettingLocked(name)) {
@@ -3291,7 +3300,7 @@
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
- if (prefix != null && !setting.getName().startsWith(prefix)) {
+ if (shouldExcludeSettingFromReset(setting, prefix)) {
continue;
}
if (setting.isDefaultFromSystem()) {
@@ -3316,7 +3325,7 @@
for (String name : settingsState.getSettingNamesLocked()) {
Setting setting = settingsState.getSettingLocked(name);
boolean someSettingChanged = false;
- if (prefix != null && !setting.getName().startsWith(prefix)) {
+ if (shouldExcludeSettingFromReset(setting, prefix)) {
continue;
}
if (setting.isDefaultFromSystem()) {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index eaf0dcb..a945c33 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -464,6 +464,31 @@
}
}
+ // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other
+ // settings are reset. But it should still be possible to explicitly set its value.
+ @Test
+ public void testSecureFrpModeSettingCannotBeReset() throws Exception {
+ final String name = Settings.Global.SECURE_FRP_MODE;
+ final String origValue = getSetting(SETTING_TYPE_GLOBAL, name);
+ setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false);
+ try {
+ assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name));
+ for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) {
+ resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+ resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES);
+ resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS);
+ }
+ // The value should still be "1". It should not have been reset to null.
+ assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name));
+ // It should still be possible to explicitly set the value to "0".
+ setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false);
+ assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name));
+ } finally {
+ setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false);
+ assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name));
+ }
+ }
+
private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
// Make sure we have a clean slate.
deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
diff --git a/packages/SystemUI/compose/core/OWNERS b/packages/SystemUI/compose/core/OWNERS
new file mode 100644
index 0000000..7e37f4f
--- /dev/null
+++ b/packages/SystemUI/compose/core/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+nijamkin@google.com
+
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
deleted file mode 100644
index c58c162..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import kotlin.math.roundToInt
-
-/**
- * This is an example Compose feature, which shows a text and a count that is incremented when
- * clicked. We also show the max width available to this component, which is displayed either next
- * to or below the text depending on that max width.
- */
-@Composable
-fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
- BoxWithConstraints(modifier) {
- val maxWidth = maxWidth
- if (maxWidth < 600.dp) {
- Column {
- CounterTile(text)
- Spacer(Modifier.size(16.dp))
- MaxWidthTile(maxWidth)
- }
- } else {
- Row {
- CounterTile(text)
- Spacer(Modifier.size(16.dp))
- MaxWidthTile(maxWidth)
- }
- }
- }
-}
-
-@Composable
-private fun CounterTile(text: String, modifier: Modifier = Modifier) {
- Surface(
- modifier,
- color = MaterialTheme.colorScheme.primaryContainer,
- shape = RoundedCornerShape(28.dp),
- ) {
- var count by remember { mutableStateOf(0) }
- Column(
- Modifier.clickable { count++ }.padding(16.dp),
- ) {
- Text(text)
- Text("I was clicked $count times.")
- }
- }
-}
-
-@Composable
-private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
- Surface(
- modifier,
- color = MaterialTheme.colorScheme.tertiaryContainer,
- shape = RoundedCornerShape(28.dp),
- ) {
- Text(
- "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
- Modifier.padding(16.dp)
- )
- }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 3dfdbba..f91baf2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -77,7 +77,7 @@
SceneTransitionLayout(
currentScene = currentSceneKey.toTransitionSceneKey(),
- onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) },
+ onChangeScene = viewModel::onSceneChanged,
transitions = transitions {},
state = state,
modifier = modifier.fillMaxSize(),
@@ -154,3 +154,7 @@
is UserAction.Back -> Back
}
}
+
+private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) {
+ onSceneChanged(sceneKey.toModel())
+}
diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
deleted file mode 100644
index 1c2e8fa..0000000
--- a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui
-
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ExampleFeatureTest {
- @get:Rule val composeRule = createComposeRule()
-
- @Test
- fun testProvidedTextIsDisplayed() {
- composeRule.setContent { ExampleFeature("foo") }
-
- composeRule.onNodeWithText("foo").assertIsDisplayed()
- }
-
- @Test
- fun testCountIsIncreasedWhenClicking() {
- composeRule.setContent { ExampleFeature("foo") }
-
- composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick()
- composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed()
- }
-}
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 763930d..c2dba6c 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,4 +38,6 @@
protected. -->
<bool name="flag_battery_shield_icon">false</bool>
+ <!-- Whether face auth will immediately stop when the display state is OFF -->
+ <bool name="flag_stop_face_auth_on_display_off">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ee9b132..0befb3b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3053,13 +3053,6 @@
<string name="wallet_quick_affordance_unavailable_configure_the_app">To add the Wallet app as a shortcut, make sure at least one card has been added</string>
<!--
- Requirement for the QR code scanner functionality to be available for the user to use. This is
- shown as part of a bulleted list of requirements. When all requirements are met, the piece of
- functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
- -->
- <string name="qr_scanner_quick_affordance_unavailable_explanation">To add the QR code scanner as a shortcut, make sure a camera app is installed</string>
-
- <!--
Explains that the lock screen shortcut for the "home" app is not available because the app isn't
installed. This is shown as part of a dialog that explains to the user why they cannot select
this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index fac2f91..3605ac2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -336,6 +336,14 @@
@Override
public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof Task)) {
+ return false;
+ }
+
// Check that the id matches
Task t = (Task) o;
return key.equals(t.key);
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 22cdb30..2abb7a4 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -33,6 +33,7 @@
import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED
import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED
import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
+import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF
import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED
import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED
import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
@@ -131,6 +132,7 @@
const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
"Face auth stopped because non strong biometric allowed changed"
const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed."
+ const val DISPLAY_OFF = "Face auth stopped due to display state OFF."
}
/**
@@ -221,7 +223,8 @@
FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
@UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED),
- @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION);
+ @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION),
+ @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF);
override fun getId(): Int = this.id
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 461d390..bb799fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -27,6 +27,7 @@
override var userId: Int = 0,
override var listening: Boolean = false,
// keep sorted
+ var allowedDisplayState: Boolean = false,
var alternateBouncerShowing: Boolean = false,
var authInterruptActive: Boolean = false,
var biometricSettingEnabledForUser: Boolean = false,
@@ -57,6 +58,8 @@
userId.toString(),
listening.toString(),
// keep sorted
+ allowedDisplayState.toString(),
+ alternateBouncerShowing.toString(),
authInterruptActive.toString(),
biometricSettingEnabledForUser.toString(),
bouncerFullyShown.toString(),
@@ -74,7 +77,6 @@
supportsDetect.toString(),
switchingUser.toString(),
systemUser.toString(),
- alternateBouncerShowing.toString(),
udfpsFingerDown.toString(),
userNotTrustedOrDetectionIsNeeded.toString(),
)
@@ -96,7 +98,9 @@
userId = model.userId
listening = model.listening
// keep sorted
+ allowedDisplayState = model.allowedDisplayState
alternateBouncerShowing = model.alternateBouncerShowing
+ authInterruptActive = model.authInterruptActive
biometricSettingEnabledForUser = model.biometricSettingEnabledForUser
bouncerFullyShown = model.bouncerFullyShown
faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated
@@ -105,7 +109,6 @@
faceLockedOut = model.faceLockedOut
goingToSleep = model.goingToSleep
keyguardAwake = model.keyguardAwake
- goingToSleep = model.goingToSleep
keyguardGoingAway = model.keyguardGoingAway
listeningForFaceAssistant = model.listeningForFaceAssistant
occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth
@@ -140,6 +143,8 @@
"userId",
"listening",
// keep sorted
+ "allowedDisplayState",
+ "alternateBouncerShowing",
"authInterruptActive",
"biometricSettingEnabledForUser",
"bouncerFullyShown",
@@ -157,7 +162,6 @@
"supportsDetect",
"switchingUser",
"systemUser",
- "udfpsBouncerShowing",
"udfpsFingerDown",
"userNotTrustedOrDetectionIsNeeded",
)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 03d9eb3..59ee0d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -32,6 +32,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Trace;
@@ -71,6 +72,8 @@
private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
private DisappearAnimationListener mDisappearAnimationListener;
+ private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
+ private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
public KeyguardPasswordView(Context context) {
this(context, null);
@@ -148,7 +151,10 @@
@Override
protected void setPasswordEntryEnabled(boolean enabled) {
- mPasswordEntry.setEnabled(enabled);
+ int color = mPasswordEntry.getTextColors().getColorForState(
+ enabled ? ENABLE_STATE_SET : DISABLE_STATE_SET, 0);
+ mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(color));
+ mPasswordEntry.setCursorVisible(enabled);
}
@Override
@@ -189,17 +195,18 @@
if (controller.isCancelled()) {
return;
}
+ float value = (float) animation.getAnimatedValue();
+ float fraction = anim.getAnimatedFraction();
Insets shownInsets = controller.getShownStateInsets();
int dist = (int) (-shownInsets.bottom / 4
- * anim.getAnimatedFraction());
+ * fraction);
Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, dist));
if (mDisappearAnimationListener != null) {
mDisappearAnimationListener.setTranslationY(-dist);
}
- controller.setInsetsAndAlpha(insets,
- (float) animation.getAnimatedValue(),
- anim.getAnimatedFraction());
+ controller.setInsetsAndAlpha(insets, value, fraction);
+ setAlpha(value);
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index dc1ddc7..42a4e72 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1197,8 +1197,6 @@
});
mPopup.show();
});
-
- mUserSwitcherViewGroup.setAlpha(0f);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index aff2591..4e1cbc7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -476,18 +476,16 @@
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
// When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mSceneInteractor.get().getTransitions(),
- sceneTransitionModel -> {
- if (sceneTransitionModel != null
- && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
- && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
- final int selectedUserId = mUserInteractor.getSelectedUserId();
- showNextSecurityScreenOrFinish(
- /* authenticated= */ true,
- selectedUserId,
- /* bypassSecondaryLockScreen= */ true,
- mSecurityModel.getSecurityMode(selectedUserId));
- }
+ mSceneInteractor.get().finishedSceneTransitions(
+ /* from= */ SceneKey.Bouncer.INSTANCE,
+ /* to= */ SceneKey.Gone.INSTANCE),
+ unused -> {
+ final int selectedUserId = mUserInteractor.getSelectedUserId();
+ showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ selectedUserId,
+ /* bypassSecondaryLockScreen= */ true,
+ mSecurityModel.getSecurityMode(selectedUserId));
});
}
}
@@ -677,6 +675,14 @@
mSecurityViewFlipperController.reset();
}
+ /** Prepares views in the bouncer before starting appear animation. */
+ public void prepareToShow() {
+ View bouncerUserSwitcher = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+ if (bouncerUserSwitcher != null) {
+ bouncerUserSwitcher.setAlpha(0f);
+ }
+ }
+
@Override
public void onResume(int reason) {
if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
@@ -827,7 +833,8 @@
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser());
- if (securityMode == SecurityMode.None || isLockscreenDisabled) {
+
+ if (securityMode == SecurityMode.None) {
finish = isLockscreenDisabled;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d950c917..5b9b53e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -41,6 +41,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
@@ -135,6 +136,7 @@
import android.text.TextUtils;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.view.Display;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -175,6 +177,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -335,6 +338,25 @@
}
}
};
+ private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() {
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ return;
+ }
+
+ if (mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState()
+ == Display.STATE_OFF) {
+ mAllowedDisplayStateForFaceAuth = false;
+ updateFaceListeningState(
+ BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_DISPLAY_OFF
+ );
+ } else {
+ mAllowedDisplayStateForFaceAuth = true;
+ }
+ }
+ };
private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
@@ -355,6 +377,7 @@
private boolean mOccludingAppRequestingFp;
private boolean mOccludingAppRequestingFace;
private boolean mSecureCameraLaunched;
+ private boolean mAllowedDisplayStateForFaceAuth = true;
@VisibleForTesting
protected boolean mTelephonyCapable;
private boolean mAllowFingerprintOnCurrentOccludingActivity;
@@ -403,6 +426,7 @@
private KeyguardFaceAuthInteractor mFaceAuthInteractor;
private final TaskStackChangeListeners mTaskStackChangeListeners;
private final IActivityTaskManager mActivityTaskManager;
+ private final DisplayTracker mDisplayTracker;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
@DevicePostureInt
@@ -2187,6 +2211,7 @@
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
Assert.isMainThread();
+ mAllowedDisplayStateForFaceAuth = true;
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
@@ -2342,7 +2367,8 @@
Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
FeatureFlags featureFlags,
TaskStackChangeListeners taskStackChangeListeners,
- IActivityTaskManager activityTaskManagerService) {
+ IActivityTaskManager activityTaskManagerService,
+ DisplayTracker displayTracker) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2390,6 +2416,10 @@
.collect(Collectors.toSet());
mTaskStackChangeListeners = taskStackChangeListeners;
mActivityTaskManager = activityTaskManagerService;
+ mDisplayTracker = displayTracker;
+ if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
+ mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+ }
mHandler = new Handler(mainLooper) {
@Override
@@ -3199,7 +3229,8 @@
&& (!mSecureCameraLaunched || mAlternateBouncerShowing)
&& faceAndFpNotAuthenticated
&& !mGoingToSleep
- && isPostureAllowedForFaceAuth;
+ && isPostureAllowedForFaceAuth
+ && mAllowedDisplayStateForFaceAuth;
// Aggregate relevant fields for debug logging.
logListenerModelData(
@@ -3207,6 +3238,7 @@
System.currentTimeMillis(),
user,
shouldListen,
+ mAllowedDisplayStateForFaceAuth,
mAlternateBouncerShowing,
mAuthInterruptActive,
biometricEnabledForUser,
@@ -4400,6 +4432,7 @@
mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
mTrustManager.unregisterTrustListener(this);
+ mDisplayTracker.removeCallback(mDisplayCallback);
mHandler.removeCallbacksAndMessages(null);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 4845a61..951a6ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -25,6 +25,7 @@
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
@@ -39,6 +40,7 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -613,12 +615,7 @@
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
- mVibrator.vibrate(
- Process.myUid(),
- getContext().getOpPackageName(),
- UdfpsController.EFFECT_CLICK,
- "lock-icon-down",
- TOUCH_VIBRATION_ATTRIBUTES);
+ vibrateOnTouchExploration();
}
// The pointer that causes ACTION_DOWN is always at index 0.
@@ -699,13 +696,8 @@
mOnGestureDetectedRunnable.run();
}
- // play device entry haptic (same as biometric success haptic)
- mVibrator.vibrate(
- Process.myUid(),
- getContext().getOpPackageName(),
- UdfpsController.EFFECT_CLICK,
- "lock-screen-lock-icon-longpress",
- TOUCH_VIBRATION_ATTRIBUTES);
+ // play device entry haptic (consistent with UDFPS controller longpress)
+ vibrateOnLongPress();
mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
}
@@ -753,6 +745,37 @@
});
}
+ @VisibleForTesting
+ void vibrateOnTouchExploration() {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mVibrator.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.CONTEXT_CLICK
+ );
+ } else {
+ mVibrator.vibrate(
+ Process.myUid(),
+ getContext().getOpPackageName(),
+ UdfpsController.EFFECT_CLICK,
+ "lock-icon-down",
+ TOUCH_VIBRATION_ATTRIBUTES);
+ }
+ }
+
+ @VisibleForTesting
+ void vibrateOnLongPress() {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS);
+ } else {
+ mVibrator.vibrate(
+ Process.myUid(),
+ getContext().getOpPackageName(),
+ UdfpsController.EFFECT_CLICK,
+ "lock-screen-lock-icon-longpress",
+ TOUCH_VIBRATION_ATTRIBUTES);
+ }
+ }
+
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
index b34f1b4..b81d7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -178,6 +178,11 @@
mainBatteryDrawable.charging = charging
}
+ /** Returns whether the battery is currently charging. */
+ fun getCharging(): Boolean {
+ return mainBatteryDrawable.charging
+ }
+
/** Sets the current level (out of 100) of the battery. */
fun setBatteryLevel(level: Int) {
mainBatteryDrawable.setBatteryLevel(level)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 4e8383c..ca43705 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -77,8 +77,9 @@
private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable;
private String mEstimateText = null;
- private boolean mCharging;
+ private boolean mPluggedIn;
private boolean mIsBatteryDefender;
+ private boolean mIsIncompatibleCharging;
private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
@@ -202,10 +203,10 @@
* @param pluggedIn whether the device is plugged in or not
*/
public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
- mDrawable.setCharging(pluggedIn);
- mDrawable.setBatteryLevel(level);
- mCharging = pluggedIn;
+ mPluggedIn = pluggedIn;
mLevel = level;
+ mDrawable.setCharging(isCharging());
+ mDrawable.setBatteryLevel(level);
updatePercentText();
}
@@ -224,6 +225,15 @@
}
}
+ void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+ boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging;
+ mIsIncompatibleCharging = isIncompatibleCharging;
+ if (valueChanged) {
+ mDrawable.setCharging(isCharging());
+ updateContentDescription();
+ }
+ }
+
private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
@@ -263,7 +273,7 @@
}
if (mBatteryPercentView != null) {
- if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
+ if (mShowPercentMode == MODE_ESTIMATE && !isCharging()) {
mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
(String estimate) -> {
if (mBatteryPercentView == null) {
@@ -316,7 +326,7 @@
} else if (mIsBatteryDefender) {
contentDescription =
context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
- } else if (mCharging) {
+ } else if (isCharging()) {
contentDescription =
context.getString(R.string.accessibility_battery_level_charging, mLevel);
} else {
@@ -462,16 +472,24 @@
}
}
+ private boolean isCharging() {
+ return mPluggedIn && !mIsIncompatibleCharging;
+ }
+
public void dump(PrintWriter pw, String[] args) {
String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + "";
String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + "";
+ String charging = mDrawable == null ? null : mDrawable.getCharging() + "";
CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
pw.println(" BatteryMeterView:");
pw.println(" mDrawable.getPowerSave: " + powerSave);
pw.println(" mDrawable.getDisplayShield: " + displayShield);
+ pw.println(" mDrawable.getCharging: " + charging);
pw.println(" mBatteryPercentView.getText(): " + percent);
pw.println(" mTextColor: #" + Integer.toHexString(mTextColor));
pw.println(" mBatteryStateUnknown: " + mBatteryStateUnknown);
+ pw.println(" mIsIncompatibleCharging: " + mIsIncompatibleCharging);
+ pw.println(" mPluggedIn: " + mPluggedIn);
pw.println(" mLevel: " + mLevel);
pw.println(" mMode: " + mShowPercentMode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 6a5749c..0ca3883 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -32,6 +32,8 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocation;
@@ -50,6 +52,7 @@
private final TunerService mTunerService;
private final Handler mMainHandler;
private final ContentResolver mContentResolver;
+ private final FeatureFlags mFeatureFlags;
private final BatteryController mBatteryController;
private final String mSlotBattery;
@@ -99,6 +102,13 @@
}
@Override
+ public void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+ if (mFeatureFlags.isEnabled(Flags.INCOMPATIBLE_CHARGING_BATTERY_ICON)) {
+ mView.onIsIncompatibleChargingChanged(isIncompatibleCharging);
+ }
+ }
+
+ @Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(super.toString());
pw.println(" location=" + mLocation);
@@ -129,6 +139,7 @@
TunerService tunerService,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController) {
super(view);
mLocation = location;
@@ -137,6 +148,7 @@
mTunerService = tunerService;
mMainHandler = mainHandler;
mContentResolver = contentResolver;
+ mFeatureFlags = featureFlags;
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index ffcae1ca..1bf3a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -116,12 +116,12 @@
repository.setMessage(
message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
)
- sceneInteractor.setCurrentScene(
+ sceneInteractor.changeScene(
scene = SceneModel(SceneKey.Bouncer),
loggingReason = "request to unlock device while authentication required",
)
} else {
- sceneInteractor.setCurrentScene(
+ sceneInteractor.changeScene(
scene = SceneModel(SceneKey.Gone),
loggingReason = "request to unlock device while authentication isn't required",
)
@@ -169,7 +169,7 @@
authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
if (isAuthenticated) {
- sceneInteractor.setCurrentScene(
+ sceneInteractor.changeScene(
scene = SceneModel(SceneKey.Gone),
loggingReason = "successful authentication",
)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index d9ec5d0..56f1cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -121,7 +121,7 @@
view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
if (isShowing) {
// Reset security container because these views are not reinflated.
- securityContainerController.reset()
+ securityContainerController.prepareToShow()
securityContainerController.reinflateViewFlipper {
// Reset Security Container entirely.
securityContainerController.onBouncerVisibilityChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e60600c..2526fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -290,6 +290,11 @@
teamfood = true
)
+ /** Stop running face auth when the display state changes to OFF. */
+ // TODO(b/294221702): Tracking bug.
+ @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(245,
+ R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -370,6 +375,9 @@
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
unreleasedFlag(614, "incompatible_charging_battery_icon")
+ // TODO(b/293585143): Tracking Bug
+ val INSTANT_TETHER = unreleasedFlag(615, "instant_tether")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 20ed549..45277b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -78,16 +78,8 @@
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
return when {
- !controller.isAvailableOnDevice ->
+ !isEnabledForPickerStateOption() ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !controller.isAbleToOpenCameraApp -> {
- KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- explanation =
- context.getString(
- R.string.qr_scanner_quick_affordance_unavailable_explanation
- ),
- )
- }
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
}
}
@@ -118,6 +110,11 @@
}
}
+ /** Returns whether QR scanner be shown as one of available lockscreen shortcut option. */
+ private fun isEnabledForPickerStateOption(): Boolean {
+ return controller.isAbleToLaunchScannerActivity && controller.isAllowedOnLockScreen
+ }
+
companion object {
private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index fa3f878f..2d460a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -120,6 +120,7 @@
mUserTracker = userTracker;
mConfigEnableLockScreenButton = mContext.getResources().getBoolean(
android.R.bool.config_enableQrCodeScannerOnLockScreen);
+ mExecutor.execute(this::updateQRCodeScannerActivityDetails);
}
/**
@@ -158,18 +159,18 @@
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
*/
public boolean isEnabledForLockScreenButton() {
- return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
+ return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
}
- /** Returns whether the feature is available on the device. */
- public boolean isAvailableOnDevice() {
+ /** Returns whether the QR scanner button is allowed on lockscreen. */
+ public boolean isAllowedOnLockScreen() {
return mConfigEnableLockScreenButton;
}
/**
- * Returns true if the feature can open a camera app on the device.
+ * Returns true if the feature can open the configured QR scanner activity.
*/
- public boolean isAbleToOpenCameraApp() {
+ public boolean isAbleToLaunchScannerActivity() {
return mIntent != null && isActivityCallable(mIntent);
}
@@ -355,9 +356,6 @@
// Reset cached values to default as we are no longer listening
mOnDefaultQRCodeScannerChangedListener = null;
- mQRCodeScannerActivity = null;
- mIntent = null;
- mComponentName = null;
}
private void notifyQRCodeScannerActivityChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 7523d6e..ddd9463 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -7,6 +7,7 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -549,10 +550,8 @@
return mPages.get(0).mRecords.size();
}
- public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
- if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
- // Do not start the reveal animation unless there are tiles to animate, multiple
- // TileLayouts available and the user has not already started dragging.
+ public void startTileReveal(Set<String> tilesToReveal, final Runnable postAnimation) {
+ if (shouldNotRunAnimation(tilesToReveal)) {
return;
}
@@ -560,13 +559,13 @@
final TileLayout lastPage = mPages.get(lastPageNumber);
final ArrayList<Animator> bounceAnims = new ArrayList<>();
for (TileRecord tr : lastPage.mRecords) {
- if (tileSpecs.contains(tr.tile.getTileSpec())) {
+ if (tilesToReveal.contains(tr.tile.getTileSpec())) {
bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
}
}
if (bounceAnims.isEmpty()) {
- // All tileSpecs are on the first page. Nothing to do.
+ // All tilesToReveal are on the first page. Nothing to do.
// TODO: potentially show a bounce animation for first page QS tiles
endFakeDrag();
return;
@@ -588,6 +587,16 @@
postInvalidateOnAnimation();
}
+ private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
+ boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2;
+ boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag();
+ // isRunningInTestHarness() to disable animation in functional testing as it caused
+ // flakiness and is not needed there. Alternative solutions were more complex and would
+ // still be either potentially flaky or modify internal data.
+ // For more info see b/253493927 and b/293234595
+ return noAnimationNeeded || scrollingInProgress || ActivityManager.isRunningInTestHarness();
+ }
+
private int sanitizePageAction(int action) {
int pageLeftId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT.getId();
int pageRightId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT.getId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 9e365d3..1ba377b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -120,7 +120,7 @@
state.label = mContext.getString(R.string.qr_code_scanner_title);
state.contentDescription = state.label;
state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
- state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
+ state.state = mQRCodeScannerController.isAbleToLaunchScannerActivity() ? Tile.STATE_INACTIVE
: Tile.STATE_UNAVAILABLE;
// The assumption is that if the OEM has the QR code scanner module enabled then the scanner
// would go to "Unavailable" state only when GMS core is updating.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index fee3960..350fa38 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -18,50 +18,49 @@
package com.android.systemui.scene.data.repository
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
/** Source of truth for scene framework application state. */
class SceneContainerRepository
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val config: SceneContainerConfig,
) {
+ private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey))
+ val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow()
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
- private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey))
- val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow()
-
- private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
- val transitionProgress: Flow<Float> =
- transitionState.flatMapLatest { observableTransitionStateFlow ->
- observableTransitionStateFlow?.flatMapLatest { observableTransitionState ->
- when (observableTransitionState) {
- is ObservableTransitionState.Idle -> flowOf(1f)
- is ObservableTransitionState.Transition -> observableTransitionState.progress
- }
- }
- ?: flowOf(1f)
- }
-
- private val _transitions = MutableStateFlow<SceneTransitionModel?>(null)
- val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow()
+ private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
+ private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
+ val transitionState: StateFlow<ObservableTransitionState> =
+ _transitionState
+ .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = defaultTransitionState,
+ )
/**
- * Returns the keys to all scenes in the container with the given name.
+ * Returns the keys to all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
@@ -70,40 +69,19 @@
return config.sceneKeys
}
- /** Sets the current scene in the container with the given name. */
- fun setCurrentScene(scene: SceneModel) {
+ fun setDesiredScene(scene: SceneModel) {
check(allSceneKeys().contains(scene.key)) {
"""
- Cannot set current scene key to "${scene.key}". The configuration does not contain a
- scene with that key.
+ Cannot set the desired scene key to "${scene.key}". The configuration does not
+ contain a scene with that key.
"""
.trimIndent()
}
- _currentScene.value = scene
+ _desiredScene.value = scene
}
- /** Sets the scene transition in the container with the given name. */
- fun setSceneTransition(from: SceneKey, to: SceneKey) {
- check(allSceneKeys().contains(from)) {
- """
- Cannot set current scene key to "$from". The configuration does not contain a scene
- with that key.
- """
- .trimIndent()
- }
- check(allSceneKeys().contains(to)) {
- """
- Cannot set current scene key to "$to". The configuration does not contain a scene
- with that key.
- """
- .trimIndent()
- }
-
- _transitions.value = SceneTransitionModel(from = from, to = to)
- }
-
- /** Sets whether the container with the given name is visible. */
+ /** Sets whether the container is visible. */
fun setVisible(isVisible: Boolean) {
_isVisible.value = isVisible
}
@@ -114,6 +92,6 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
- this.transitionState.value = transitionState
+ _transitionState.value = transitionState
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 64715bc..cf7abdd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -23,12 +23,15 @@
import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
/**
* Generic business logic and app state accessors for the scene framework.
@@ -46,7 +49,54 @@
) {
/**
- * Returns the keys of all scenes in the container with the given name.
+ * The currently *desired* scene.
+ *
+ * **Important:** this value will _commonly be different_ from what is being rendered in the UI,
+ * by design.
+ *
+ * There are two intended sources for this value:
+ * 1. Programmatic requests to transition to another scene (calls to [changeScene]).
+ * 2. Reports from the UI about completing a transition to another scene (calls to
+ * [onSceneChanged]).
+ *
+ * Both the sources above cause the value of this flow to change; however, they cause mismatches
+ * in different ways.
+ *
+ * **Updates from programmatic transitions**
+ *
+ * When an external bit of code asks the framework to switch to another scene, the value here
+ * will update immediately. Downstream, the UI will detect this change and initiate the
+ * transition animation. As the transition animation progresses, a threshold will be reached, at
+ * which point the UI and the state here will match each other.
+ *
+ * **Updates from the UI**
+ *
+ * When the user interacts with the UI, the UI runs a transition animation that tracks the user
+ * pointer (for example, the user's finger). During this time, the state value here and what the
+ * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the
+ * change, making the value here match the UI again.
+ */
+ val desiredScene: StateFlow<SceneModel> = repository.desiredScene
+
+ /**
+ * The current state of the transition.
+ *
+ * Consumers should use this state to know:
+ * 1. Whether there is an ongoing transition or if the system is at rest.
+ * 2. When transitioning, which scenes are being transitioned between.
+ * 3. When transitioning, what the progress of the transition is.
+ */
+ val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+
+ /** Whether the scene container is visible. */
+ val isVisible: StateFlow<Boolean> = repository.isVisible
+
+ private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
+ /** A flow of motion events originating from outside of the scene framework. */
+ val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
+
+ /**
+ * Returns the keys of all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
@@ -55,26 +105,20 @@
return repository.allSceneKeys()
}
- /** Sets the scene in the container with the given name. */
- fun setCurrentScene(scene: SceneModel, loggingReason: String) {
- val currentSceneKey = repository.currentScene.value.key
- if (currentSceneKey == scene.key) {
- return
- }
-
- logger.logSceneChange(
- from = currentSceneKey,
- to = scene.key,
- reason = loggingReason,
- )
- repository.setCurrentScene(scene)
- repository.setSceneTransition(from = currentSceneKey, to = scene.key)
+ /**
+ * Requests a scene change to the given scene.
+ *
+ * The change is animated. Therefore, while the value in [desiredScene] will update immediately,
+ * it will be some time before the UI will switch to the desired scene. The scene change
+ * requested is remembered here but served by the UI layer, which will start a transition
+ * animation. Once enough of the transition has occurred, the system will come into agreement
+ * between the [desiredScene] and the UI.
+ */
+ fun changeScene(scene: SceneModel, loggingReason: String) {
+ updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested)
}
- /** The current scene in the container with the given name. */
- val currentScene: StateFlow<SceneModel> = repository.currentScene
-
- /** Sets the visibility of the container with the given name. */
+ /** Sets the visibility of the container. */
fun setVisible(isVisible: Boolean, loggingReason: String) {
val wasVisible = repository.isVisible.value
if (wasVisible == isVisible) {
@@ -89,9 +133,6 @@
return repository.setVisible(isVisible)
}
- /** Whether the container with the given name is visible. */
- val isVisible: StateFlow<Boolean> = repository.isVisible
-
/**
* Binds the given flow so the system remembers it.
*
@@ -101,23 +142,53 @@
repository.setTransitionState(transitionState)
}
- /** Progress of the transition into the current scene in the container with the given name. */
- val transitionProgress: Flow<Float> = repository.transitionProgress
-
/**
- * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
- * transition occurs. The flow begins with a `null` value at first, because the initial scene is
- * not something that we transition to from another scene.
+ * Returns a stream of events that emits one [Unit] every time the framework transitions from
+ * [from] to [to].
*/
- val transitions: StateFlow<SceneTransitionModel?> = repository.transitions
-
- private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
-
- /** A flow of motion events originating from outside of the scene framework. */
- val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
+ fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> {
+ return transitionState
+ .mapNotNull { it as? ObservableTransitionState.Idle }
+ .map { idleState -> idleState.scene }
+ .distinctUntilChanged()
+ .pairwise()
+ .mapNotNull { (previousSceneKey, currentSceneKey) ->
+ Unit.takeIf { previousSceneKey == from && currentSceneKey == to }
+ }
+ }
/** Handles a remote user input. */
fun onRemoteUserInput(input: RemoteUserInput) {
_remoteUserInput.value = input
}
+
+ /**
+ * Notifies that the UI has transitioned sufficiently to the given scene.
+ *
+ * *Not intended for external use!*
+ *
+ * Once a transition between one scene and another passes a threshold, the UI invokes this
+ * method to report it, updating the value in [desiredScene] to match what the UI shows.
+ */
+ internal fun onSceneChanged(scene: SceneModel, loggingReason: String) {
+ updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted)
+ }
+
+ private fun updateDesiredScene(
+ scene: SceneModel,
+ loggingReason: String,
+ log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit,
+ ) {
+ val currentSceneKey = desiredScene.value.key
+ if (currentSceneKey == scene.key) {
+ return
+ }
+
+ log(
+ /* from= */ currentSceneKey,
+ /* to= */ scene.key,
+ /* loggingReason= */ loggingReason,
+ )
+ repository.setDesiredScene(scene)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index bd233f8..afefccb 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -30,6 +30,7 @@
import com.android.systemui.model.updateFlags
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
@@ -40,8 +41,8 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
/**
@@ -73,14 +74,31 @@
}
}
- /** Updates the visibility of the scene container based on the current scene. */
+ /** Updates the visibility of the scene container. */
private fun hydrateVisibility() {
applicationScope.launch {
- sceneInteractor.currentScene
- .map { it.key }
+ sceneInteractor.transitionState
+ .mapNotNull { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> {
+ if (state.scene != SceneKey.Gone) {
+ true to "scene is not Gone"
+ } else {
+ false to "scene is Gone"
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (state.fromScene == SceneKey.Gone) {
+ true to "scene transitioning away from Gone"
+ } else {
+ null
+ }
+ }
+ }
+ }
.distinctUntilChanged()
- .collect { sceneKey ->
- sceneInteractor.setVisible(sceneKey != SceneKey.Gone, "scene is $sceneKey")
+ .collect { (isVisible, loggingReason) ->
+ sceneInteractor.setVisible(isVisible, loggingReason)
}
}
}
@@ -89,43 +107,55 @@
private fun automaticallySwitchScenes() {
applicationScope.launch {
authenticationInteractor.isUnlocked
- .map { isUnlocked ->
- val currentSceneKey = sceneInteractor.currentScene.value.key
+ .mapNotNull { isUnlocked ->
+ val renderedScenes =
+ when (val transitionState = sceneInteractor.transitionState.value) {
+ is ObservableTransitionState.Idle -> setOf(transitionState.scene)
+ is ObservableTransitionState.Transition ->
+ setOf(
+ transitionState.progress,
+ transitionState.toScene,
+ )
+ }
val isBypassEnabled = authenticationInteractor.isBypassEnabled()
when {
isUnlocked ->
- when (currentSceneKey) {
+ when {
// When the device becomes unlocked in Bouncer, go to Gone.
- is SceneKey.Bouncer ->
+ renderedScenes.contains(SceneKey.Bouncer) ->
SceneKey.Gone to "device unlocked in Bouncer scene"
+
// When the device becomes unlocked in Lockscreen, go to Gone if
// bypass is enabled.
- is SceneKey.Lockscreen ->
+ renderedScenes.contains(SceneKey.Lockscreen) ->
if (isBypassEnabled) {
SceneKey.Gone to
"device unlocked in Lockscreen scene with bypass"
} else {
null
}
+
// We got unlocked while on a scene that's not Lockscreen or
// Bouncer, no need to change scenes.
else -> null
}
+
// When the device becomes locked, to Lockscreen.
!isUnlocked ->
- when (currentSceneKey) {
+ when {
// Already on lockscreen or bouncer, no need to change scenes.
- is SceneKey.Lockscreen,
- is SceneKey.Bouncer -> null
+ renderedScenes.contains(SceneKey.Lockscreen) ||
+ renderedScenes.contains(SceneKey.Bouncer) -> null
+
// We got locked while on a scene that's not Lockscreen or Bouncer,
// go to Lockscreen.
else ->
- SceneKey.Lockscreen to "device locked in $currentSceneKey scene"
+ SceneKey.Lockscreen to
+ "device locked in non-Lockscreen and non-Bouncer scene"
}
else -> null
}
}
- .filterNotNull()
.collect { (targetSceneKey, loggingReason) ->
switchToScene(
targetSceneKey = targetSceneKey,
@@ -143,7 +173,7 @@
WakefulnessState.STARTING_TO_SLEEP -> {
switchToScene(
targetSceneKey = SceneKey.Lockscreen,
- loggingReason = "device is asleep",
+ loggingReason = "device is starting to sleep",
)
}
WakefulnessState.STARTING_TO_WAKE -> {
@@ -165,8 +195,9 @@
/** Keeps [SysUiState] up-to-date */
private fun hydrateSystemUiState() {
applicationScope.launch {
- sceneInteractor.currentScene
- .map { it.key }
+ sceneInteractor.transitionState
+ .mapNotNull { it as? ObservableTransitionState.Idle }
+ .map { it.scene }
.distinctUntilChanged()
.collect { sceneKey ->
sysUiState.updateFlags(
@@ -183,7 +214,7 @@
}
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
- sceneInteractor.setCurrentScene(
+ sceneInteractor.changeScene(
scene = SceneModel(targetSceneKey),
loggingReason = loggingReason,
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index 0adbd5a..62136dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -37,7 +37,7 @@
)
}
- fun logSceneChange(
+ fun logSceneChangeRequested(
from: SceneKey,
to: SceneKey,
reason: String,
@@ -50,7 +50,24 @@
str2 = to.toString()
str3 = reason
},
- messagePrinter = { "$str1 → $str2, reason: $str3" },
+ messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" },
+ )
+ }
+
+ fun logSceneChangeCommitted(
+ from: SceneKey,
+ to: SceneKey,
+ reason: String,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = from.toString()
+ str2 = to.toString()
+ str3 = reason
+ },
+ messagePrinter = { "Scene change committed: $str1 → $str2, reason: $str3" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
deleted file mode 100644
index c8f46a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2023 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.scene.shared.model
-
-/** Models a transition between two scenes. */
-data class SceneTransitionModel(
- /** The scene we transitioned away from. */
- val from: SceneKey,
- /** The scene we transitioned into. */
- val to: SceneKey,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index b4ebaec..3e9bbe4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -45,15 +45,15 @@
*/
val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
- /** The current scene. */
- val currentScene: StateFlow<SceneModel> = interactor.currentScene
+ /** The scene that should be rendered. */
+ val currentScene: StateFlow<SceneModel> = interactor.desiredScene
/** Whether the container is visible. */
val isVisible: StateFlow<Boolean> = interactor.isVisible
- /** Requests a transition to the scene with the given key. */
- fun setCurrentScene(scene: SceneModel) {
- interactor.setCurrentScene(
+ /** Notifies that the UI has transitioned sufficiently to the given scene. */
+ fun onSceneChanged(scene: SceneModel) {
+ interactor.onSceneChanged(
scene = scene,
loggingReason = SCENE_TRANSITION_LOGGING_REASON,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 76d0f6e..05a0416 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -19,6 +19,7 @@
import android.content.ClipData
import android.content.ClipDescription
import android.content.ComponentName
+import android.content.ContentProvider
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -36,7 +37,9 @@
fun createShareWithText(uri: Uri, extraText: String): Intent =
createShare(uri, text = extraText)
- private fun createShare(uri: Uri, subject: String? = null, text: String? = null): Intent {
+ private fun createShare(rawUri: Uri, subject: String? = null, text: String? = null): Intent {
+ val uri = uriWithoutUserId(rawUri)
+
// Create a share intent, this will always go through the chooser activity first
// which should not trigger auto-enter PiP
val sharingIntent =
@@ -68,7 +71,8 @@
* @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if
* available.
*/
- fun createEditIntent(uri: Uri, context: Context): Intent {
+ fun createEdit(rawUri: Uri, context: Context): Intent {
+ val uri = uriWithoutUserId(rawUri)
val editIntent = Intent(Intent.ACTION_EDIT)
val editor = context.getString(R.string.config_screenshotEditor)
@@ -84,3 +88,12 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
}
+
+/**
+ * URIs here are passed only via Intent which are sent to the target user via Intent. Because of
+ * this, the userId component can be removed to prevent compatibility issues when an app attempts
+ * valid a URI containing a userId within the authority.
+ */
+private fun uriWithoutUserId(uri: Uri): Uri {
+ return ContentProvider.getUriWithoutUserId(uri)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 010658b..53dbe76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -334,7 +334,7 @@
if (mScreenshotUserHandle != Process.myUserHandle()) {
// TODO: Fix transition for work profile. Omitting it in the meantime.
mActionExecutor.launchIntentAsync(
- ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+ ActionIntentCreator.INSTANCE.createEdit(uri, this),
null,
mScreenshotUserHandle, false);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 204b5e6..3903bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -815,7 +815,7 @@
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
prepareSharedTransition();
mActionExecutor.launchIntentAsync(
- ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+ ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
imageData.editTransition.get().bundle,
imageData.owner, true);
});
@@ -823,7 +823,7 @@
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
prepareSharedTransition();
mActionExecutor.launchIntentAsync(
- ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+ ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
imageData.editTransition.get().bundle,
imageData.owner, true);
});
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
index bb7f721..d5571d4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -48,6 +48,9 @@
/** Remove a [Callback] previously added. */
fun removeCallback(callback: Callback)
+ /** Gets the Display with the given displayId */
+ fun getDisplay(displayId: Int): Display
+
/** Ćallback for notifying of changes. */
interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
index 5169f88..68cc483 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -115,6 +115,10 @@
}
}
+ override fun getDisplay(displayId: Int): Display {
+ return displayManager.getDisplay(displayId)
+ }
+
@WorkerThread
private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) {
Assert.isNotMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 2955118..6e76784 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -273,6 +273,7 @@
tunerService: TunerService,
@Main mainHandler: Handler,
contentResolver: ContentResolver,
+ featureFlags: FeatureFlags,
batteryController: BatteryController,
): BatteryMeterViewController {
return BatteryMeterViewController(
@@ -283,6 +284,7 @@
tunerService,
mainHandler,
contentResolver,
+ featureFlags,
batteryController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 5c72731..e763797 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -99,6 +99,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.NamedListenerSet;
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
@@ -161,7 +162,8 @@
private final HashMap<String, FutureDismissal> mFutureDismissals = new HashMap<>();
@Nullable private CollectionReadyForBuildListener mBuildListener;
- private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
+ private final NamedListenerSet<NotifCollectionListener>
+ mNotifCollectionListeners = new NamedListenerSet<>();
private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
@@ -236,7 +238,7 @@
/** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
void addCollectionListener(NotifCollectionListener listener) {
Assert.isMainThread();
- mNotifCollectionListeners.add(listener);
+ mNotifCollectionListeners.addIfAbsent(listener);
}
/** @see NotifPipeline#removeCollectionListener(NotifCollectionListener) */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
index d95d593..5acc50a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -21,7 +21,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.Assert
import com.android.systemui.util.ListenerSet
-import com.android.systemui.util.isNotEmpty
import com.android.systemui.util.traceSection
import java.util.Collections.unmodifiableList
import java.util.concurrent.Executor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 0205523..240ae0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -69,6 +69,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.NamedListenerSet;
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
@@ -121,14 +122,14 @@
private final List<NotifSection> mNotifSections = new ArrayList<>();
private NotifStabilityManager mNotifStabilityManager;
- private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
- new ArrayList<>();
- private final List<OnBeforeSortListener> mOnBeforeSortListeners =
- new ArrayList<>();
- private final List<OnBeforeFinalizeFilterListener> mOnBeforeFinalizeFilterListeners =
- new ArrayList<>();
- private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners =
- new ArrayList<>();
+ private final NamedListenerSet<OnBeforeTransformGroupsListener>
+ mOnBeforeTransformGroupsListeners = new NamedListenerSet<>();
+ private final NamedListenerSet<OnBeforeSortListener>
+ mOnBeforeSortListeners = new NamedListenerSet<>();
+ private final NamedListenerSet<OnBeforeFinalizeFilterListener>
+ mOnBeforeFinalizeFilterListeners = new NamedListenerSet<>();
+ private final NamedListenerSet<OnBeforeRenderListListener>
+ mOnBeforeRenderListListeners = new NamedListenerSet<>();
@Nullable private OnRenderListListener mOnRenderListListener;
private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@@ -184,28 +185,28 @@
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mOnBeforeTransformGroupsListeners.add(listener);
+ mOnBeforeTransformGroupsListeners.addIfAbsent(listener);
}
void addOnBeforeSortListener(OnBeforeSortListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mOnBeforeSortListeners.add(listener);
+ mOnBeforeSortListeners.addIfAbsent(listener);
}
void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mOnBeforeFinalizeFilterListeners.add(listener);
+ mOnBeforeFinalizeFilterListeners.addIfAbsent(listener);
}
void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mOnBeforeRenderListListeners.add(listener);
+ mOnBeforeRenderListListeners.addIfAbsent(listener);
}
void addPreRenderInvalidator(Invalidator invalidator) {
@@ -496,7 +497,9 @@
mTempSectionMembers.add(entry);
}
}
+ Trace.beginSection(section.getLabel());
section.getSectioner().onEntriesUpdated(mTempSectionMembers);
+ Trace.endSection();
mTempSectionMembers.clear();
}
Trace.endSection();
@@ -1430,33 +1433,33 @@
private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups");
- for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
- mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
- }
+ mOnBeforeTransformGroupsListeners.forEachTraced(listener -> {
+ listener.onBeforeTransformGroups(entries);
+ });
Trace.endSection();
}
private void dispatchOnBeforeSort(List<ListEntry> entries) {
Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort");
- for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
- mOnBeforeSortListeners.get(i).onBeforeSort(entries);
- }
+ mOnBeforeSortListeners.forEachTraced(listener -> {
+ listener.onBeforeSort(entries);
+ });
Trace.endSection();
}
private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter");
- for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) {
- mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries);
- }
+ mOnBeforeFinalizeFilterListeners.forEachTraced(listener -> {
+ listener.onBeforeFinalizeFilter(entries);
+ });
Trace.endSection();
}
private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList");
- for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
- mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
- }
+ mOnBeforeRenderListListeners.forEachTraced(listener -> {
+ listener.onBeforeRenderList(entries);
+ });
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 6500ff7..73decfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -23,6 +23,7 @@
import android.annotation.IntDef;
import android.os.RemoteException;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -342,11 +343,13 @@
private void inflateEntry(NotificationEntry entry,
NotifUiAdjustment newAdjustment,
String reason) {
+ Trace.beginSection("PrepCoord.inflateEntry");
abortInflation(entry, reason);
mInflationAdjustments.put(entry, newAdjustment);
mInflatingNotifs.add(entry);
NotifInflater.Params params = getInflaterParams(newAdjustment, reason);
mNotifInflater.inflateViews(entry, params, this::onInflationFinished);
+ Trace.endSection();
}
private void rebind(NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
index e20f0e5..e06e2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -22,6 +22,8 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.NamedListenerSet
+import com.android.systemui.util.traceSection
/**
* Set of classes that represent the various events that [NotifCollection] can dispatch to
@@ -30,10 +32,10 @@
* These events build up in a queue and are periodically emitted in chunks by the collection.
*/
-sealed class NotifEvent {
- fun dispatchTo(listeners: List<NotifCollectionListener>) {
- for (i in listeners.indices) {
- dispatchToListener(listeners[i])
+sealed class NotifEvent(private val traceName: String) {
+ fun dispatchTo(listeners: NamedListenerSet<NotifCollectionListener>) {
+ traceSection(traceName) {
+ listeners.forEachTraced(::dispatchToListener)
}
}
@@ -43,7 +45,7 @@
data class BindEntryEvent(
val entry: NotificationEntry,
val sbn: StatusBarNotification
-) : NotifEvent() {
+) : NotifEvent("onEntryBind") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryBind(entry, sbn)
}
@@ -51,7 +53,7 @@
data class InitEntryEvent(
val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryInit") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryInit(entry)
}
@@ -59,7 +61,7 @@
data class EntryAddedEvent(
val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryAdded") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryAdded(entry)
}
@@ -68,7 +70,7 @@
data class EntryUpdatedEvent(
val entry: NotificationEntry,
val fromSystem: Boolean
-) : NotifEvent() {
+) : NotifEvent(if (fromSystem) "onEntryUpdated" else "onEntryUpdated fromSystem=true") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryUpdated(entry, fromSystem)
}
@@ -77,7 +79,7 @@
data class EntryRemovedEvent(
val entry: NotificationEntry,
val reason: Int
-) : NotifEvent() {
+) : NotifEvent("onEntryRemoved ${cancellationReasonDebugString(reason)}") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryRemoved(entry, reason)
}
@@ -85,7 +87,7 @@
data class CleanUpEntryEvent(
val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryCleanUp") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryCleanUp(entry)
}
@@ -93,13 +95,13 @@
data class RankingUpdatedEvent(
val rankingMap: RankingMap
-) : NotifEvent() {
+) : NotifEvent("onRankingUpdate") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onRankingUpdate(rankingMap)
}
}
-class RankingAppliedEvent() : NotifEvent() {
+class RankingAppliedEvent : NotifEvent("onRankingApplied") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onRankingApplied()
}
@@ -110,7 +112,7 @@
val user: UserHandle,
val channel: NotificationChannel,
val modificationType: Int
-) : NotifEvent() {
+) : NotifEvent("onNotificationChannelModified") {
override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onNotificationChannelModified(pkgName, user, channel, modificationType)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
index fd5bae1..c873e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
@@ -26,7 +26,6 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.util.Assert
import com.android.systemui.util.ListenerSet
-import com.android.systemui.util.isNotEmpty
import java.io.PrintWriter
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index d896541..9d95342 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
@@ -95,7 +96,7 @@
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun createIcons(entry: NotificationEntry) {
+ fun createIcons(entry: NotificationEntry) = traceSection("IconManager.createIcons") {
// Construct the status bar icon view.
val sbIcon = iconBuilder.createIconView(entry)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
@@ -143,9 +144,9 @@
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun updateIcons(entry: NotificationEntry) {
+ fun updateIcons(entry: NotificationEntry) = traceSection("IconManager.updateIcons") {
if (!entry.icons.areIconsAvailable) {
- return
+ return@traceSection
}
entry.icons.smallIconDescriptor = null
entry.icons.peopleAvatarDescriptor = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index a4e8c2e..80f5d19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -21,12 +21,16 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -71,6 +75,10 @@
@NotificationRowScope
public class ExpandableNotificationRowController implements NotifViewController {
private static final String TAG = "NotifRowController";
+
+ static final Uri BUBBLES_SETTING_URI =
+ Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+ private static final String BUBBLES_SETTING_ENABLED_VALUE = "1";
private final ExpandableNotificationRow mView;
private final NotificationListContainer mListContainer;
private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
@@ -104,6 +112,23 @@
private final ExpandableNotificationRowDragController mDragController;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final IStatusBarService mStatusBarService;
+
+ private final NotificationSettingsController mSettingsController;
+
+ @VisibleForTesting
+ final NotificationSettingsController.Listener mSettingsListener =
+ new NotificationSettingsController.Listener() {
+ @Override
+ public void onSettingChanged(Uri setting, int userId, String value) {
+ if (BUBBLES_SETTING_URI.equals(setting)) {
+ final int viewUserId = mView.getEntry().getSbn().getUserId();
+ if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) {
+ mView.getPrivateLayout().setBubblesEnabledForUser(
+ BUBBLES_SETTING_ENABLED_VALUE.equals(value));
+ }
+ }
+ }
+ };
private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
@Override
@@ -201,6 +226,7 @@
FeatureFlags featureFlags,
PeopleNotificationIdentifier peopleNotificationIdentifier,
Optional<BubblesManager> bubblesManagerOptional,
+ NotificationSettingsController settingsController,
ExpandableNotificationRowDragController dragController,
NotificationDismissibilityProvider dismissibilityProvider,
IStatusBarService statusBarService) {
@@ -229,6 +255,7 @@
mFeatureFlags = featureFlags;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
+ mSettingsController = settingsController;
mDragController = dragController;
mMetricsLogger = metricsLogger;
mChildrenContainerLogger = childrenContainerLogger;
@@ -298,12 +325,14 @@
NotificationMenuRowPlugin.class, false /* Allow multiple */);
mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
}
@Override
public void onViewDetachedFromWindow(View v) {
mPluginManager.removePluginListener(mView);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 20f4429..f4f78d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -41,6 +41,8 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.annotation.MainThread;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
@@ -65,7 +67,6 @@
import com.android.systemui.statusbar.policy.SmartReplyView;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
-import com.android.systemui.wmshell.BubblesManager;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -134,6 +135,7 @@
private PeopleNotificationIdentifier mPeopleIdentifier;
private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
private IStatusBarService mStatusBarService;
+ private boolean mBubblesEnabledForUser;
/**
* List of listeners for when content views become inactive (i.e. not the showing view).
@@ -1440,12 +1442,20 @@
}
}
+ @MainThread
+ public void setBubblesEnabledForUser(boolean enabled) {
+ mBubblesEnabledForUser = enabled;
+
+ applyBubbleAction(mExpandedChild, mNotificationEntry);
+ applyBubbleAction(mHeadsUpChild, mNotificationEntry);
+ }
+
@VisibleForTesting
boolean shouldShowBubbleButton(NotificationEntry entry) {
boolean isPersonWithShortcut =
mPeopleIdentifier.getPeopleNotificationType(entry)
>= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
- return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
+ return mBubblesEnabledForUser
&& isPersonWithShortcut
&& entry.getBubbleMetadata() != null;
}
@@ -2079,6 +2089,7 @@
pw.print("null");
}
pw.println();
+ pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);
pw.print("RemoteInputViews { ");
pw.print(" visibleType: " + mVisibleType);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 9bc0333..7134f15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -48,6 +48,7 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.transition.ChangeBounds;
@@ -118,6 +119,8 @@
private NotificationGuts mGutsContainer;
private OnConversationSettingsClickListener mOnConversationSettingsClickListener;
+ private UserManager mUm;
+
@VisibleForTesting
boolean mSkipPost = false;
private int mActualHeight;
@@ -155,7 +158,9 @@
// People Tile add request.
if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
mShadeController.animateCollapseShade();
- mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
+ if (mUm.isSameProfileGroup(UserHandle.USER_SYSTEM, mSbn.getNormalizedUserId())) {
+ mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
+ }
}
mGutsContainer.closeControls(v, /* save= */ true);
};
@@ -188,6 +193,7 @@
public void bindNotification(
ShortcutManager shortcutManager,
PackageManager pm,
+ UserManager um,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
INotificationManager iNotificationManager,
OnUserInteractionCallback onUserInteractionCallback,
@@ -211,6 +217,7 @@
mEntry = entry;
mSbn = entry.getSbn();
mPm = pm;
+ mUm = um;
mAppName = mPackageName;
mOnSettingsClickListener = onSettingsClick;
mNotificationChannel = notificationChannel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7dbca42..6f79ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -30,6 +30,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -112,6 +113,9 @@
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
+
+ private final UserManager mUserManager;
+
private final LauncherApps mLauncherApps;
private final ShortcutManager mShortcutManager;
private final UserContextProvider mContextTracker;
@@ -128,6 +132,7 @@
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
+ UserManager userManager,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,
@@ -150,6 +155,7 @@
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
+ mUserManager = userManager;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mLauncherApps = launcherApps;
mShortcutManager = shortcutManager;
@@ -471,6 +477,7 @@
notificationInfoView.bindNotification(
mShortcutManager,
pmUser,
+ mUserManager,
mPeopleSpaceWidgetManager,
mNotificationManager,
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
new file mode 100644
index 0000000..51e4537
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 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.notification.row;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.inject.Inject;
+
+/**
+ * Centralized controller for listening to Secure Settings changes and informing in-process
+ * listeners, on a background thread.
+ */
+@SysUISingleton
+public class NotificationSettingsController implements Dumpable {
+
+ private final static String TAG = "NotificationSettingsController";
+ private final UserTracker mUserTracker;
+ private final UserTracker.Callback mCurrentUserTrackerCallback;
+ private final Handler mMainHandler;
+ private final Handler mBackgroundHandler;
+ private final ContentObserver mContentObserver;
+ private final SecureSettings mSecureSettings;
+ private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>();
+
+ @Inject
+ public NotificationSettingsController(UserTracker userTracker,
+ @Main Handler mainHandler,
+ @Background Handler backgroundHandler,
+ SecureSettings secureSettings,
+ DumpManager dumpManager) {
+ mUserTracker = userTracker;
+ mMainHandler = mainHandler;
+ mBackgroundHandler = backgroundHandler;
+ mSecureSettings = secureSettings;
+ mContentObserver = new ContentObserver(mBackgroundHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ synchronized (mListeners) {
+ if (mListeners.containsKey(uri)) {
+ int userId = mUserTracker.getUserId();
+ String value = getCurrentSettingValue(uri, userId);
+ for (Listener listener : mListeners.get(uri)) {
+ mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
+ }
+ }
+ }
+ }
+ };
+
+ mCurrentUserTrackerCallback = new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ synchronized (mListeners) {
+ if (mListeners.size() > 0) {
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ for (Uri uri : mListeners.keySet()) {
+ mSecureSettings.registerContentObserverForUser(
+ uri, false, mContentObserver, newUser);
+ }
+ }
+ }
+ }
+ };
+ mUserTracker.addCallback(
+ mCurrentUserTrackerCallback, new HandlerExecutor(mBackgroundHandler));
+
+ dumpManager.registerNormalDumpable(TAG, this);
+ }
+
+ /**
+ * Register a callback whenever the given secure settings changes.
+ *
+ * On registration, will trigger the listener on the main thread with the current value of
+ * the setting.
+ */
+ @Main
+ public void addCallback(@NonNull Uri uri, @NonNull Listener listener) {
+ if (uri == null || listener == null) {
+ return;
+ }
+ synchronized (mListeners) {
+ ArrayList<Listener> currentListeners = mListeners.get(uri);
+ if (currentListeners == null) {
+ currentListeners = new ArrayList<>();
+ }
+ if (!currentListeners.contains(listener)) {
+ currentListeners.add(listener);
+ }
+ mListeners.put(uri, currentListeners);
+ if (currentListeners.size() == 1) {
+ mSecureSettings.registerContentObserverForUser(
+ uri, false, mContentObserver, mUserTracker.getUserId());
+ }
+ }
+ mBackgroundHandler.post(() -> {
+ int userId = mUserTracker.getUserId();
+ String value = getCurrentSettingValue(uri, userId);
+ mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
+ });
+
+ }
+
+ public void removeCallback(Uri uri, Listener listener) {
+ synchronized (mListeners) {
+ ArrayList<Listener> currentListeners = mListeners.get(uri);
+
+ if (currentListeners != null) {
+ currentListeners.remove(listener);
+ }
+ if (currentListeners == null || currentListeners.size() == 0) {
+ mListeners.remove(uri);
+ }
+
+ if (mListeners.size() == 0) {
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ }
+ }
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ synchronized (mListeners) {
+ pw.println("Settings Uri Listener List:");
+ for (Uri uri : mListeners.keySet()) {
+ pw.println(" Uri=" + uri);
+ for (Listener listener : mListeners.get(uri)) {
+ pw.println(" Listener=" + listener.getClass().getName());
+ }
+ }
+ }
+ }
+
+ private String getCurrentSettingValue(Uri uri, int userId) {
+ final String setting = uri == null ? null : uri.getLastPathSegment();
+ return mSecureSettings.getStringForUser(setting, userId);
+ }
+
+ /**
+ * Listener invoked whenever settings are changed.
+ */
+ public interface Listener {
+ @MainThread
+ void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value);
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b11b472..b29d461 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -49,6 +49,9 @@
const val COL_NAME_IS_ENABLED = "isEnabled"
/** Column name to use for [isWifiDefault] for table logging. */
const val COL_NAME_IS_DEFAULT = "isDefault"
+
+ const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
+ "Wifi network was carrier merged but had invalid sub ID"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
index 7d2501ca..ab9b516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -24,6 +24,7 @@
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -56,12 +57,14 @@
val activity = getString("activity").toActivity()
val ssid = getString("ssid")
val validated = getString("fully").toBoolean()
+ val hotspotDeviceType = getString("hotspot").toHotspotDeviceType()
return FakeWifiEventModel.Wifi(
level = level,
activity = activity,
ssid = ssid,
validated = validated,
+ hotspotDeviceType,
)
}
@@ -82,6 +85,20 @@
else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
}
+ private fun String?.toHotspotDeviceType(): WifiNetworkModel.HotspotDeviceType {
+ return when (this) {
+ null,
+ "none" -> WifiNetworkModel.HotspotDeviceType.NONE
+ "unknown" -> WifiNetworkModel.HotspotDeviceType.UNKNOWN
+ "phone" -> WifiNetworkModel.HotspotDeviceType.PHONE
+ "tablet" -> WifiNetworkModel.HotspotDeviceType.TABLET
+ "laptop" -> WifiNetworkModel.HotspotDeviceType.LAPTOP
+ "watch" -> WifiNetworkModel.HotspotDeviceType.WATCH
+ "auto" -> WifiNetworkModel.HotspotDeviceType.AUTO
+ else -> WifiNetworkModel.HotspotDeviceType.INVALID
+ }
+ }
+
companion object {
const val DEFAULT_CARRIER_MERGED_SUB_ID = 10
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index a57be66..99b68005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -97,6 +97,7 @@
isValidated = validated ?: true,
level = level ?: 0,
ssid = ssid ?: DEMO_NET_SSID,
+ hotspotDeviceType = hotspotDeviceType,
// These fields below aren't supported in demo mode, since they aren't needed to satisfy
// the interface.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
index f5035cbc..b2e843e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model
import android.telephony.Annotation
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
/**
* Model for demo wifi commands, ported from [NetworkControllerImpl]
@@ -29,6 +30,8 @@
@Annotation.DataActivityType val activity: Int,
val ssid: String?,
val validated: Boolean?,
+ val hotspotDeviceType: WifiNetworkModel.HotspotDeviceType =
+ WifiNetworkModel.HotspotDeviceType.NONE,
) : FakeWifiEventModel
data class CarrierMerged(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
new file mode 100644
index 0000000..f1b98b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 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.wifi.data.repository.prod
+
+import android.net.wifi.WifiManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Object to provide shared helper functions between [WifiRepositoryImpl] and
+ * [WifiRepositoryViaTrackerLib].
+ */
+object WifiRepositoryHelper {
+ /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
+ fun createActivityFlow(
+ wifiManager: WifiManager,
+ @Main mainExecutor: Executor,
+ scope: CoroutineScope,
+ tableLogBuffer: TableLogBuffer,
+ inputLogger: (String) -> Unit,
+ ): StateFlow<DataActivityModel> {
+ return conflatedCallbackFlow {
+ val callback =
+ WifiManager.TrafficStateCallback { state ->
+ inputLogger.invoke(prettyPrintActivity(state))
+ trySend(state.toWifiDataActivityModel())
+ }
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+ }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
+ )
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = ACTIVITY_DEFAULT,
+ )
+ }
+
+ // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+ private fun prettyPrintActivity(activity: Int): String {
+ return when (activity) {
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+ else -> "INVALID"
+ }
+ }
+
+ private const val ACTIVITY_PREFIX = "wifiActivity"
+ val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 995de6d..afd1576 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -28,7 +28,6 @@
import android.net.NetworkRequest
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -40,10 +39,10 @@
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
@@ -218,29 +217,15 @@
)
override val wifiActivity: StateFlow<DataActivityModel> =
- conflatedCallbackFlow {
- val callback = TrafficStateCallback { state ->
- logger.logActivity(prettyPrintActivity(state))
- trySend(state.toWifiDataActivityModel())
- }
- wifiManager.registerTrafficStateCallback(mainExecutor, callback)
- awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
- }
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = ACTIVITY_PREFIX,
- initialValue = ACTIVITY_DEFAULT,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ACTIVITY_DEFAULT,
- )
+ WifiRepositoryHelper.createActivityFlow(
+ wifiManager,
+ mainExecutor,
+ scope,
+ wifiTableLogBuffer,
+ logger::logActivity,
+ )
companion object {
- private const val ACTIVITY_PREFIX = "wifiActivity"
-
- val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
// Start out with no known wifi network.
// Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
// initial fetch to get a starting wifi network. But, it uses a deprecated API
@@ -277,6 +262,8 @@
isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
wifiInfo.ssid,
+ // This repository doesn't support any hotspot information.
+ WifiNetworkModel.HotspotDeviceType.NONE,
wifiInfo.isPasspointAp,
wifiInfo.isOsuAp,
wifiInfo.passpointProviderFriendlyName
@@ -284,16 +271,6 @@
}
}
- private fun prettyPrintActivity(activity: Int): String {
- return when (activity) {
- TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
- TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
- TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
- TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
- else -> "INVALID"
- }
- }
-
private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
NetworkRequest.Builder()
.clearCapabilities()
@@ -301,9 +278,6 @@
.addTransportType(TRANSPORT_WIFI)
.addTransportType(TRANSPORT_CELLULAR)
.build()
-
- private const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
- "Wifi network was carrier merged but had invalid sub ID"
}
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index 1271367..175563b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -17,12 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.net.wifi.WifiManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.table.TableLogBuffer
@@ -31,20 +34,25 @@
import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
+import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
import com.android.wifitrackerlib.WifiPickerTracker
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
@@ -62,6 +70,7 @@
class WifiRepositoryViaTrackerLib
@Inject
constructor(
+ featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
@@ -75,6 +84,8 @@
mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
}
+ private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
+
private var wifiPickerTracker: WifiPickerTracker? = null
private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
@@ -128,19 +139,21 @@
}
}
- // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all
- // available wifi networks every 10s. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running. We should disable these
- // scans (ideal) or at least run them very infrequently.
- wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback)
+ wifiPickerTracker =
+ wifiPickerTrackerFactory.create(lifecycle, callback).apply {
+ // By default, [WifiPickerTracker] will scan to see all available wifi
+ // networks in the area. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running (and in fact,
+ // running scans is costly and should be avoided whenever possible).
+ this?.disableScanning()
+ }
// The lifecycle must be STARTED in order for the callback to receive events.
mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
awaitClose {
mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
}
}
- // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows)
- .stateIn(scope, SharingStarted.WhileSubscribed(), current)
+ .stateIn(scope, SharingStarted.Eagerly, current)
}
override val isWifiEnabled: StateFlow<Boolean> =
@@ -153,7 +166,7 @@
columnName = COL_NAME_IS_ENABLED,
initialValue = false,
)
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ .stateIn(scope, SharingStarted.Eagerly, false)
override val wifiNetwork: StateFlow<WifiNetworkModel> =
wifiPickerTrackerInfo
@@ -164,7 +177,7 @@
columnPrefix = "",
initialValue = WIFI_NETWORK_DEFAULT,
)
- .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT)
+ .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
/** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
@@ -172,30 +185,58 @@
return WIFI_NETWORK_DEFAULT
}
return if (this is MergedCarrierEntry) {
+ this.convertCarrierMergedToModel()
+ } else {
+ this.convertNormalToModel()
+ }
+ }
+
+ private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+ return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+ WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+ } else {
WifiNetworkModel.CarrierMerged(
networkId = NETWORK_ID,
- // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry].
- subscriptionId = TEMP_SUB_ID,
+ subscriptionId = this.subscriptionId,
level = this.level,
// WifiManager APIs to calculate the signal level start from 0, so
// maxSignalLevel + 1 represents the total level buckets count.
numberOfLevels = wifiManager.maxSignalLevel + 1,
)
- } else {
- WifiNetworkModel.Active(
- networkId = NETWORK_ID,
- isValidated = this.hasInternetAccess(),
- level = this.level,
- ssid = this.ssid,
- // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be
- // appropriate).
- isPasspointAccessPoint = false,
- isOnlineSignUpForPasspointAccessPoint = false,
- passpointProviderFriendlyName = null,
- )
}
}
+ private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+ if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+ // If our level means the network is unreachable or the level is otherwise invalid, we
+ // don't have an active network.
+ return WifiNetworkModel.Inactive
+ }
+
+ val hotspotDeviceType =
+ if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+ this.deviceType.toHotspotDeviceType()
+ } else {
+ WifiNetworkModel.HotspotDeviceType.NONE
+ }
+
+ return WifiNetworkModel.Active(
+ networkId = NETWORK_ID,
+ isValidated = this.hasInternetAccess(),
+ level = this.level,
+ ssid = this.title,
+ hotspotDeviceType = hotspotDeviceType,
+ // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
+ // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+ // always be false/null in this repository.
+ // TODO(b/292534484): Remove these fields from the wifi network model once this
+ // repository is fully enabled.
+ isPasspointAccessPoint = false,
+ isOnlineSignUpForPasspointAccessPoint = false,
+ passpointProviderFriendlyName = null,
+ )
+ }
+
override val isWifiDefault: StateFlow<Boolean> =
wifiPickerTrackerInfo
.map { it.isDefault }
@@ -206,12 +247,16 @@
columnName = COL_NAME_IS_DEFAULT,
initialValue = false,
)
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ .stateIn(scope, SharingStarted.Eagerly, false)
- // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since
- // WifiTrackerLib doesn't expose activity details.
override val wifiActivity: StateFlow<DataActivityModel> =
- MutableStateFlow(DataActivityModel(false, false))
+ WifiRepositoryHelper.createActivityFlow(
+ wifiManager,
+ mainExecutor,
+ scope,
+ wifiTrackerLibTableLogBuffer,
+ this::logActivity,
+ )
private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
inputLogger.log(
@@ -231,6 +276,10 @@
)
}
+ private fun logActivity(activity: String) {
+ inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
+ }
+
/**
* Data class storing all the information fetched from [WifiPickerTracker].
*
@@ -249,6 +298,7 @@
class Factory
@Inject
constructor(
+ private val featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
@@ -257,6 +307,7 @@
) {
fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
return WifiRepositoryViaTrackerLib(
+ featureFlags,
scope,
mainExecutor,
wifiPickerTrackerFactory,
@@ -283,13 +334,5 @@
* to [WifiRepositoryViaTrackerLib].
*/
private const val NETWORK_ID = -1
-
- /**
- * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the
- * subscription ID.
- *
- * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1.
- */
- private const val TEMP_SUB_ID = -2
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 4b33c88..7078a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.pipeline.wifi.shared.model
import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
import android.telephony.SubscriptionManager
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
@@ -52,6 +54,7 @@
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
row.logChange(COL_SSID, null)
+ row.logChange(COL_HOTSPOT, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
@@ -83,6 +86,7 @@
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
row.logChange(COL_SSID, null)
+ row.logChange(COL_HOTSPOT, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
@@ -110,6 +114,7 @@
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
row.logChange(COL_SSID, null)
+ row.logChange(COL_HOTSPOT, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
@@ -184,6 +189,7 @@
row.logChange(COL_LEVEL, level)
row.logChange(COL_NUM_LEVELS, numberOfLevels)
row.logChange(COL_SSID, null)
+ row.logChange(COL_HOTSPOT, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
@@ -209,6 +215,12 @@
/** See [android.net.wifi.WifiInfo.ssid]. */
val ssid: String? = null,
+ /**
+ * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this
+ * isn't a hotspot connection.
+ */
+ val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+
/** See [android.net.wifi.WifiInfo.isPasspointAp]. */
val isPasspointAccessPoint: Boolean = false,
@@ -247,6 +259,9 @@
if (prevVal.ssid != ssid) {
row.logChange(COL_SSID, ssid)
}
+ if (prevVal.hotspotDeviceType != hotspotDeviceType) {
+ row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
+ }
// TODO(b/238425913): The passpoint-related values are frequently never used, so it
// would be great to not log them when they're not used.
@@ -272,6 +287,7 @@
row.logChange(COL_LEVEL, level)
row.logChange(COL_NUM_LEVELS, null)
row.logChange(COL_SSID, ssid)
+ row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
@@ -298,13 +314,51 @@
}
companion object {
+ // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead
+ // once the migration to WifiTrackerLib is complete.
@VisibleForTesting internal const val MAX_VALID_LEVEL = 4
}
}
companion object {
+ // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead
+ // once the migration to WifiTrackerLib is complete.
@VisibleForTesting internal const val MIN_VALID_LEVEL = 0
}
+
+ /**
+ * Enum for the type of device providing the hotspot connection, or [NONE] if this connection
+ * isn't a hotspot.
+ */
+ enum class HotspotDeviceType {
+ /* This wifi connection isn't a hotspot. */
+ NONE,
+ /** The device type for this hotspot is unknown. */
+ UNKNOWN,
+ PHONE,
+ TABLET,
+ LAPTOP,
+ WATCH,
+ AUTO,
+ /** The device type sent for this hotspot is invalid to SysUI. */
+ INVALID,
+ }
+
+ /**
+ * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to
+ * our internal representation.
+ */
+ fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType {
+ return when (this) {
+ NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN
+ NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE
+ NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET
+ NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP
+ NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH
+ NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO
+ else -> HotspotDeviceType.INVALID
+ }
+ }
}
const val TYPE_CARRIER_MERGED = "CarrierMerged"
@@ -319,6 +373,7 @@
const val COL_LEVEL = "level"
const val COL_NUM_LEVELS = "maxLevel"
const val COL_SSID = "ssid"
+const val COL_HOTSPOT = "hotspot"
const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 7df083afc..37eda64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -162,6 +162,9 @@
default void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
}
+ default void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+ }
+
@Override
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index d5d8f4d..4b51511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
@@ -42,6 +43,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.PowerUtil;
@@ -97,6 +99,7 @@
private boolean mAodPowerSave;
private boolean mWirelessCharging;
private boolean mIsBatteryDefender = false;
+ private boolean mIsIncompatibleCharging = false;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -136,6 +139,7 @@
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
filter.addAction(ACTION_LEVEL_TEST);
+ filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
mBroadcastDispatcher.registerReceiver(this, filter);
}
@@ -169,6 +173,7 @@
ipw.print("mCharging="); ipw.println(mCharging);
ipw.print("mCharged="); ipw.println(mCharged);
ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
+ ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging);
ipw.print("mPowerSave="); ipw.println(mPowerSave);
ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
ipw.println("Callbacks:------------------");
@@ -214,6 +219,7 @@
cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging);
cb.onIsBatteryDefenderChanged(mIsBatteryDefender);
+ cb.onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
}
@Override
@@ -229,7 +235,7 @@
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
if (mTestMode && !intent.getBooleanExtra("testmode", false)) return;
mHasReceivedBattery = true;
- mLevel = (int)(100f
+ mLevel = (int) (100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
@@ -262,6 +268,12 @@
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
+ } else if (action.equals(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED)) {
+ boolean isIncompatibleCharging = Utils.containsIncompatibleChargers(mContext, TAG);
+ if (isIncompatibleCharging != mIsIncompatibleCharging) {
+ mIsIncompatibleCharging = isIncompatibleCharging;
+ fireIsIncompatibleChargingChanged();
+ }
} else if (action.equals(ACTION_LEVEL_TEST)) {
mTestMode = true;
mMainHandler.post(new Runnable() {
@@ -270,6 +282,7 @@
int mSavedLevel = mLevel;
boolean mSavedPluggedIn = mPluggedIn;
Intent mTestIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
@Override
public void run() {
if (mCurrentLevel < 0) {
@@ -333,6 +346,13 @@
return mIsBatteryDefender;
}
+ /**
+ * Returns whether the charging adapter is incompatible.
+ */
+ public boolean isIncompatibleCharging() {
+ return mIsIncompatibleCharging;
+ }
+
@Override
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -453,6 +473,15 @@
}
}
+ private void fireIsIncompatibleChargingChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
+ }
+ }
+ }
+
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoModeController.isInDemoMode()) {
@@ -464,6 +493,7 @@
String powerSave = args.getString("powersave");
String present = args.getString("present");
String defender = args.getString("defender");
+ String incompatible = args.getString("incompatible");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -482,6 +512,10 @@
mIsBatteryDefender = defender.equals("true");
fireIsBatteryDefenderChanged();
}
+ if (incompatible != null) {
+ mIsIncompatibleCharging = incompatible.equals("true");
+ fireIsIncompatibleChargingChanged();
+ }
fireBatteryLevelChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index f8c36dc..518a9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -55,7 +55,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
@@ -362,7 +361,8 @@
private static final int MSG_ADD_CALLBACK = 3;
private static final int MSG_REMOVE_CALLBACK = 4;
- private ArrayList<LocationChangeCallback> mSettingsChangeCallbacks = new ArrayList<>();
+ private final ArrayList<LocationChangeCallback> mSettingsChangeCallbacks =
+ new ArrayList<>();
H(Looper looper) {
super(looper);
@@ -388,14 +388,23 @@
}
private void locationActiveChanged() {
- Utils.safeForeach(mSettingsChangeCallbacks,
- cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests));
+ synchronized (mSettingsChangeCallbacks) {
+ final int n = mSettingsChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mSettingsChangeCallbacks.get(i)
+ .onLocationActiveChanged(mAreActiveLocationRequests);
+ }
+ }
}
private void locationSettingsChanged() {
boolean isEnabled = isLocationEnabled();
- Utils.safeForeach(mSettingsChangeCallbacks,
- cb -> cb.onLocationSettingsChanged(isEnabled));
+ synchronized (mSettingsChangeCallbacks) {
+ final int n = mSettingsChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mSettingsChangeCallbacks.get(i).onLocationSettingsChanged(isEnabled);
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt
new file mode 100644
index 0000000..b0230b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.util
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well. Specifically, to ensure that
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
+ * the set after the iterator is constructed.
+ */
+interface IListenerSet<E : Any> : Set<E> {
+ /**
+ * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is
+ * already in the set.
+ */
+ fun addIfAbsent(element: E): Boolean
+
+ /** A thread-safe, reentrant-safe method to remove a listener. */
+ fun remove(element: E): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
index a47e614..f8e0b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -29,20 +29,12 @@
class ListenerSet<E : Any>
/** Private constructor takes the internal list so that we can use auto-delegation */
private constructor(private val listeners: CopyOnWriteArrayList<E>) :
- Collection<E> by listeners, Set<E> {
+ Collection<E> by listeners, IListenerSet<E> {
/** Create a new instance */
constructor() : this(CopyOnWriteArrayList())
- /**
- * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is
- * already in the set.
- */
- fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
+ override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
- /** A thread-safe, reentrant-safe method to remove a listener. */
- fun remove(element: E): Boolean = listeners.remove(element)
+ override fun remove(element: E): Boolean = listeners.remove(element)
}
-
-/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */
-fun <T : Any> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
new file mode 100644
index 0000000..c90b57e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 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.util
+
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.function.Consumer
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well. Specifically, to ensure that
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
+ * the set after the iterator is constructed.
+ *
+ * This class provides all the abilities of [ListenerSet], except that each listener has a name
+ * calculated at runtime which can be used for time-efficient tracing of listener invocations.
+ */
+class NamedListenerSet<E : Any>(
+ private val getName: (E) -> String = { it.javaClass.name },
+) : IListenerSet<E> {
+ private val listeners = CopyOnWriteArrayList<NamedListener>()
+
+ override val size: Int
+ get() = listeners.size
+
+ override fun isEmpty() = listeners.isEmpty()
+
+ override fun iterator(): Iterator<E> = iterator {
+ listeners.iterator().forEach { yield(it.listener) }
+ }
+
+ override fun containsAll(elements: Collection<E>) =
+ listeners.count { it.listener in elements } == elements.size
+
+ override fun contains(element: E) = listeners.firstOrNull { it.listener == element } != null
+
+ override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(NamedListener(element))
+
+ override fun remove(element: E): Boolean = listeners.removeIf { it.listener == element }
+
+ /** A wrapper for the listener with an associated name. */
+ inner class NamedListener(val listener: E) {
+ val name: String = getName(listener)
+
+ override fun hashCode(): Int {
+ return listener.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean =
+ when {
+ other === null -> false
+ other === this -> true
+ other !is NamedListenerSet<*>.NamedListener -> false
+ listener == other.listener -> true
+ else -> false
+ }
+ }
+
+ /** Iterate the listeners in the set, providing the name for each one as well. */
+ inline fun forEachNamed(block: (String, E) -> Unit) =
+ namedIterator().forEach { element -> block(element.name, element.listener) }
+
+ /**
+ * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
+ * the listener name.
+ */
+ inline fun forEachTraced(block: (E) -> Unit) = forEachNamed { name, listener ->
+ traceSection(name) { block(listener) }
+ }
+
+ /**
+ * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
+ * the listener name.
+ */
+ fun forEachTraced(consumer: Consumer<E>) = forEachNamed { name, listener ->
+ traceSection(name) { consumer.accept(listener) }
+ }
+
+ /** Iterate over the [NamedListener]s currently in the set. */
+ fun namedIterator(): Iterator<NamedListener> = listeners.iterator()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index c2727fc..e0daa070 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -37,6 +37,10 @@
/**
* Allows lambda iteration over a list. It is done in reverse order so it is safe
* to add or remove items during the iteration. Skips over null items.
+ *
+ * @deprecated According to b/286841705, this is *not* safe: If an item is removed from the
+ * list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be
+ * used; try using `synchronized` or making a copy of the list instead.
*/
public static <T> void safeForeach(List<T> list, Consumer<T> c) {
for (int i = list.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index e7d420b..9016220 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -419,7 +419,15 @@
assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
mWifiRepository.setWifiNetwork(
- new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+ new WifiNetworkModel.Active(
+ /* networkId= */ 0,
+ /* isValidated= */ false,
+ /* level= */ 0,
+ /* ssid= */ "",
+ /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE,
+ /* isPasspointAccessPoint= */ false,
+ /* isOnlineSignUpForPasspointAccessPoint= */ false,
+ /* passpointProviderFriendlyName= */ null));
assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index efb981e..9ba21da 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -27,6 +27,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
+import android.view.View
import android.view.WindowInsetsController
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -50,6 +51,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -66,6 +68,8 @@
import java.util.Optional
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -80,6 +84,7 @@
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
@@ -139,6 +144,7 @@
private lateinit var testableResources: TestableResources
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var underTest: KeyguardSecurityContainerController
@@ -198,6 +204,9 @@
whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
sceneTestUtils = SceneTestUtils(this)
sceneInteractor = sceneTestUtils.sceneInteractor()
+ sceneTransitionStateFlow =
+ MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
+ sceneInteractor.setTransitionState(sceneTransitionStateFlow)
underTest =
KeyguardSecurityContainerController(
@@ -484,6 +493,30 @@
}
@Test
+ fun showNextSecurityScreenOrFinish_SimPinToAnotherSimPin_None() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.SimPin)
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+ }
+
+ @Test
fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
val registeredSwipeListener = registeredSwipeListener
whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
@@ -733,20 +766,39 @@
// is
// not enough to trigger a dismissal of the keyguard.
underTest.onViewAttached()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ flowOf(.5f)
+ )
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
clearInvocations(viewMediatorCallback)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
@@ -754,12 +806,22 @@
// scene
// does not dismiss the keyguard while we're not listening.
underTest.onViewDetached()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// While not listening, moving back to the bouncer does not dismiss the keyguard.
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
@@ -767,11 +829,26 @@
// gone
// scene now does dismiss the keyguard again.
underTest.onViewAttached()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
}
+ @Test
+ fun testResetUserSwitcher() {
+ val userSwitcher = mock(View::class.java)
+ whenever(view.findViewById<View>(R.id.keyguard_bouncer_user_switcher))
+ .thenReturn(userSwitcher)
+
+ underTest.prepareToShow()
+ verify(userSwitcher).setAlpha(0f)
+ }
+
private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
get() {
underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5abab62..6f3322a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -40,6 +40,7 @@
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
+import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -89,6 +90,7 @@
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.display.DisplayManagerGlobal;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
@@ -121,6 +123,9 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
import androidx.annotation.NonNull;
@@ -143,6 +148,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -304,10 +310,12 @@
mFingerprintAuthenticatorsRegisteredCallback;
private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
+ private FakeDisplayTracker mDisplayTracker;
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ mDisplayTracker = new FakeDisplayTracker(mContext);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
@@ -348,6 +356,7 @@
allowTestableLooperAsMainThread();
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
+ mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false);
when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
@@ -358,6 +367,11 @@
anyInt());
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+ setupBiometrics(mKeyguardUpdateMonitor);
+ }
+
+ private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor)
+ throws RemoteException {
captureAuthenticatorsRegisteredCallbacks();
setupFaceAuth(/* isClass3 */ false);
setupFingerprintAuth(/* isClass3 */ true);
@@ -367,9 +381,9 @@
mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
biometricsEnabledForCurrentUser();
- mHandler = spy(mKeyguardUpdateMonitor.getHandler());
+ mHandler = spy(keyguardUpdateMonitor.getHandler());
try {
- FieldSetter.setField(mKeyguardUpdateMonitor,
+ FieldSetter.setField(keyguardUpdateMonitor,
KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler);
} catch (NoSuchFieldException e) {
@@ -3029,6 +3043,79 @@
verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
}
+ @Test
+ public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() {
+ assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void onDisplayOn_nothingHappens() throws RemoteException {
+ // GIVEN
+ keyguardIsVisible();
+ enableStopFaceAuthOnDisplayOff();
+
+ // WHEN the default display state changes to ON
+ triggerDefaultDisplayStateChangeToOn();
+
+ // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth,
+ // NOT the display on event
+ verifyFaceAuthenticateNeverCalled();
+ verifyFaceDetectNeverCalled();
+ }
+
+ @Test
+ public void onDisplayOff_stopFaceAuth() throws RemoteException {
+ enableStopFaceAuthOnDisplayOff();
+
+ // GIVEN device is listening for face
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ verifyFaceAuthenticateCall();
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN the default display state changes to OFF
+ triggerDefaultDisplayStateChangeToOff();
+
+ // THEN face listening is stopped.
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE)); // beverlyt
+
+ }
+
+ private void triggerDefaultDisplayStateChangeToOn() {
+ triggerDefaultDisplayStateChangeTo(true);
+ }
+
+ private void triggerDefaultDisplayStateChangeToOff() {
+ triggerDefaultDisplayStateChangeTo(false);
+ }
+
+ /**
+ * @param on true for Display.STATE_ON, else Display.STATE_OFF
+ */
+ private void triggerDefaultDisplayStateChangeTo(boolean on) {
+ DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class);
+ DisplayInfo displayInfoWithDisplayState = new DisplayInfo();
+ displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF;
+ when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId()))
+ .thenReturn(displayInfoWithDisplayState);
+ mDisplayTracker.setAllDisplays(new Display[]{
+ new Display(
+ displayManagerGlobal,
+ mDisplayTracker.getDefaultDisplayId(),
+ displayInfoWithDisplayState,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+ });
+ mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId());
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -3297,6 +3384,18 @@
mTestableLooper.processAllMessages();
}
+ private void enableStopFaceAuthOnDisplayOff() throws RemoteException {
+ cleanupKeyguardUpdateMonitor();
+ clearInvocations(mFaceManager);
+ clearInvocations(mFingerprintManager);
+ clearInvocations(mBiometricManager);
+ clearInvocations(mStatusBarStateController);
+ mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true);
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+ setupBiometrics(mKeyguardUpdateMonitor);
+ assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1);
+ }
+
private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;
@@ -3374,7 +3473,7 @@
mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
mFaceWakeUpTriggersConfig, mDevicePostureController,
Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
- mTaskStackChangeListeners, mActivityTaskManager);
+ mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index ed6a891..45021ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -20,7 +20,9 @@
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
@@ -33,11 +35,13 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
+import android.view.HapticFeedbackConstants;
import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.doze.util.BurnInHelperKt;
import org.junit.Test;
@@ -339,4 +343,59 @@
// THEN the lock icon is shown
verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ @Test
+ public void playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+ // WHEN request to vibrate on touch exploration
+ mUnderTest.vibrateOnTouchExploration();
+
+ // THEN vibrates
+ verify(mVibrator).vibrate(
+ anyInt(),
+ any(),
+ eq(UdfpsController.EFFECT_CLICK),
+ eq("lock-icon-down"),
+ any());
+ }
+
+ @Test
+ public void playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+ // WHEN request to vibrate on touch exploration
+ mUnderTest.vibrateOnTouchExploration();
+
+ // THEN performHapticFeedback is used
+ verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK));
+ }
+
+ @Test
+ public void playHaptic_onLongPress_NoOneWayHaptics_usesVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+ // WHEN request to vibrate on long press
+ mUnderTest.vibrateOnLongPress();
+
+ // THEN uses vibrate
+ verify(mVibrator).vibrate(
+ anyInt(),
+ any(),
+ eq(UdfpsController.EFFECT_CLICK),
+ eq("lock-screen-lock-icon-longpress"),
+ any());
+ }
+
+ @Test
+ public void playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+ // WHEN request to vibrate on long press
+ mUnderTest.vibrateOnLongPress();
+
+ // THEN uses perform haptic feedback
+ verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 40b5729..ec8be8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -63,6 +64,7 @@
private ContentResolver mContentResolver;
@Mock
private BatteryController mBatteryController;
+ private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
private BatteryMeterViewController mController;
@@ -160,6 +162,7 @@
mTunerService,
mHandler,
mContentResolver,
+ mFakeFeatureFlags,
mBatteryController
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index c84efac..f0f4ca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -131,6 +131,16 @@
}
@Test
+ fun contentDescription_isIncompatibleCharging_notCharging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+ mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 45)
+ )
+ }
+
+ @Test
fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
mBatteryMeterView.onBatteryLevelChanged(15, false)
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
@@ -231,14 +241,33 @@
assertThat(drawable.displayShield).isFalse()
}
+ @Test
+ fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+ assertThat(drawable.getCharging()).isFalse()
+ }
+
+ @Test
+ fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsIncompatibleChargingChanged(false)
+
+ assertThat(drawable.getCharging()).isTrue()
+ }
+
private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
return (mBatteryMeterView.getChildAt(0) as ImageView)
.drawable as AccessorizedBatteryDrawable
}
private class Fetcher : BatteryEstimateFetcher {
- override fun fetchBatteryTimeRemainingEstimate(
- completion: EstimateFetchCompletion) {
+ override fun fetchBatteryTimeRemainingEstimate(completion: EstimateFetchCompletion) {
completion.onBatteryRemainingEstimateRetrieved(ESTIMATE)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index df4d222..86e0c75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -70,7 +70,7 @@
@Test
fun pinAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
@@ -102,7 +102,7 @@
@Test
fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
@@ -139,7 +139,7 @@
@Test
fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
@@ -169,7 +169,7 @@
@Test
fun passwordAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -202,7 +202,7 @@
@Test
fun patternAuthMethod() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
@@ -236,7 +236,7 @@
@Test
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -249,7 +249,7 @@
@Test
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
utils.authenticationRepository.setLockscreenEnabled(true)
utils.authenticationRepository.setUnlocked(false)
@@ -262,7 +262,7 @@
@Test
fun showOrUnlockDevice_customMessageShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -283,7 +283,7 @@
val isThrottled by collectLastValue(underTest.isThrottled)
val throttling by collectLastValue(underTest.throttling)
val message by collectLastValue(underTest.message)
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
underTest.showOrUnlockDevice()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 4e9fe8d..4380af8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -73,14 +73,15 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -93,14 +94,15 @@
@Test
fun onPasswordInputChanged() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -115,12 +117,13 @@
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("password")
@@ -133,14 +136,15 @@
@Test
fun onAuthenticateKeyPressed_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -155,14 +159,15 @@
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 000200c..ea2cad2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -76,7 +76,7 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -84,7 +84,8 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -98,7 +99,7 @@
@Test
fun onDragStart() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -106,7 +107,8 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -122,14 +124,15 @@
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -169,7 +172,7 @@
@Test
fun onDragEnd_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -177,7 +180,8 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -201,7 +205,7 @@
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -209,7 +213,8 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 4b667c3..531f86a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -76,11 +76,13 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -93,12 +95,14 @@
@Test
fun onPinButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -113,12 +117,14 @@
@Test
fun onBackspaceButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -135,11 +141,13 @@
@Test
fun onPinEdit() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -157,12 +165,14 @@
@Test
fun onBackspaceButtonLongPressed() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -181,10 +191,12 @@
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -199,12 +211,14 @@
@Test
fun onAuthenticateButtonClicked_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -223,12 +237,14 @@
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -255,11 +271,13 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -272,13 +290,15 @@
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 7510373..9d983b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -137,27 +137,18 @@
}
@Test
- fun getPickerScreenState_enabledIfConfiguredOnDevice_canOpenCamera() = runTest {
- whenever(controller.isAvailableOnDevice).thenReturn(true)
- whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+ fun getPickerScreenState_enabledIfConfiguredOnDevice_isEnabledForPickerState() = runTest {
+ whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+ whenever(controller.isAbleToLaunchScannerActivity).thenReturn(true)
assertThat(underTest.getPickerScreenState())
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
}
@Test
- fun getPickerScreenState_disabledIfConfiguredOnDevice_cannotOpenCamera() = runTest {
- whenever(controller.isAvailableOnDevice).thenReturn(true)
- whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
-
- assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
- }
-
- @Test
- fun getPickerScreenState_unavailableIfNotConfiguredOnDevice() = runTest {
- whenever(controller.isAvailableOnDevice).thenReturn(false)
- whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+ fun getPickerScreenState_disabledIfConfiguredOnDevice_isDisabledForPickerState() = runTest {
+ whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+ whenever(controller.isAbleToLaunchScannerActivity).thenReturn(false)
assertThat(underTest.getPickerScreenState())
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 834b9c5..45d7a5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -107,7 +107,7 @@
@Test
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -120,7 +120,7 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -133,7 +133,7 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -146,7 +146,7 @@
@Test
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 65210d6..e905e9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -132,7 +132,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
}
@Test
@@ -151,7 +151,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
}
@Test
@@ -161,7 +161,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
}
@Test
@@ -171,7 +171,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
}
@Test
@@ -181,7 +181,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/abc.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
}
@Test
@@ -191,7 +191,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
}
@Test
@@ -201,24 +201,24 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
"def/.ijk", false);
verifyActivityDetails("def/.ijk");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
null, false);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
- // Once from setup + twice from this function
- verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
+ // twice from this function
+ verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
}
@Test
@@ -228,7 +228,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -236,14 +236,14 @@
verifyActivityDetails("def/.ijk");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
null, false);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
}
@@ -295,19 +295,20 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAllowedOnLockScreen()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
// Once from setup + twice from this function
verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
}
@@ -319,13 +320,13 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
+ // even if unregistered, intent and activity details are retained
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
QR_CODE_SCANNER_PREFERENCE_CHANGE);
- verifyActivityDetails(null);
- assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
+ assertThat(mController.isAllowedOnLockScreen()).isTrue();
// Unregister once again and make sure it affects the next register event
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -334,7 +335,7 @@
QR_CODE_SCANNER_PREFERENCE_CHANGE);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
}
@Test
@@ -344,7 +345,7 @@
/* enableOnLockScreen */ false);
assertThat(mController.getIntent()).isNotNull();
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+ assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
assertThat(getSettingsQRCodeDefaultComponent()).isNull();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 6f2d904..71aa7a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -117,7 +117,7 @@
@Test
public void testQRCodeTileUnavailable() {
- when(mController.isAbleToOpenCameraApp()).thenReturn(false);
+ when(mController.isAbleToLaunchScannerActivity()).thenReturn(false);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
assertEquals(state.state, Tile.STATE_UNAVAILABLE);
@@ -127,7 +127,7 @@
@Test
public void testQRCodeTileAvailable() {
- when(mController.isAbleToOpenCameraApp()).thenReturn(true);
+ when(mController.isAbleToLaunchScannerActivity()).thenReturn(true);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
assertEquals(state.state, Tile.STATE_INACTIVE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index bb365d0..2cb0205 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -56,7 +56,7 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -69,7 +69,7 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 56e3e96..181f8a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,6 +38,7 @@
class SceneContainerRepositoryTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
@Test
fun allSceneKeys() {
@@ -56,97 +56,82 @@
}
@Test
- fun currentScene() = runTest {
- val underTest = utils.fakeSceneContainerRepository()
- val currentScene by collectLastValue(underTest.currentScene)
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+ fun desiredScene() =
+ testScope.runTest {
+ val underTest = utils.fakeSceneContainerRepository()
+ val currentScene by collectLastValue(underTest.desiredScene)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene(SceneModel(SceneKey.Shade))
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
- }
+ underTest.setDesiredScene(SceneModel(SceneKey.Shade))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
+ }
@Test(expected = IllegalStateException::class)
- fun setCurrentScene_noSuchSceneInContainer_throws() {
+ fun setDesiredScene_noSuchSceneInContainer_throws() {
val underTest =
utils.fakeSceneContainerRepository(
utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
)
- underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+ underTest.setDesiredScene(SceneModel(SceneKey.Shade))
}
@Test
- fun isVisible() = runTest {
- val underTest = utils.fakeSceneContainerRepository()
- val isVisible by collectLastValue(underTest.isVisible)
- assertThat(isVisible).isTrue()
+ fun isVisible() =
+ testScope.runTest {
+ val underTest = utils.fakeSceneContainerRepository()
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isTrue()
- underTest.setVisible(false)
- assertThat(isVisible).isFalse()
+ underTest.setVisible(false)
+ assertThat(isVisible).isFalse()
- underTest.setVisible(true)
- assertThat(isVisible).isTrue()
- }
+ underTest.setVisible(true)
+ assertThat(isVisible).isTrue()
+ }
@Test
- fun transitionProgress() = runTest {
- val underTest = utils.fakeSceneContainerRepository()
- val sceneTransitionProgress by collectLastValue(underTest.transitionProgress)
- assertThat(sceneTransitionProgress).isEqualTo(1f)
+ fun transitionState_defaultsToIdle() =
+ testScope.runTest {
+ val underTest = utils.fakeSceneContainerRepository()
+ val transitionState by collectLastValue(underTest.transitionState)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(SceneKey.Lockscreen)
- )
- underTest.setTransitionState(transitionState)
- assertThat(sceneTransitionProgress).isEqualTo(1f)
-
- val progress = MutableStateFlow(1f)
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = SceneKey.Shade,
- progress = progress,
- )
- assertThat(sceneTransitionProgress).isEqualTo(1f)
-
- progress.value = 0.1f
- assertThat(sceneTransitionProgress).isEqualTo(0.1f)
-
- progress.value = 0.9f
- assertThat(sceneTransitionProgress).isEqualTo(0.9f)
-
- underTest.setTransitionState(null)
- assertThat(sceneTransitionProgress).isEqualTo(1f)
- }
+ assertThat(transitionState)
+ .isEqualTo(
+ ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ )
+ }
@Test
- fun setSceneTransition() = runTest {
- val underTest = utils.fakeSceneContainerRepository()
- val sceneTransition by collectLastValue(underTest.transitions)
- assertThat(sceneTransition).isNull()
+ fun transitionState_reflectsUpdates() =
+ testScope.runTest {
+ val underTest = utils.fakeSceneContainerRepository()
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ underTest.setTransitionState(transitionState)
+ val reflectedTransitionState by collectLastValue(underTest.transitionState)
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
- underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings)
- assertThat(sceneTransition)
- .isEqualTo(
- SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
- )
- }
+ val progress = MutableStateFlow(1f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ )
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
- @Test(expected = IllegalStateException::class)
- fun setSceneTransition_noFromSceneInContainer_throws() {
- val underTest =
- utils.fakeSceneContainerRepository(
- utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
- )
- underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
- }
+ progress.value = 0.1f
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
- @Test(expected = IllegalStateException::class)
- fun setSceneTransition_noToSceneInContainer_throws() {
- val underTest =
- utils.fakeSceneContainerRepository(
- utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
- )
- underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
- }
+ progress.value = 0.9f
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+ underTest.setTransitionState(null)
+ assertThat(reflectedTransitionState)
+ .isEqualTo(
+ ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 4facc7a..0a93a7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -25,10 +25,12 @@
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,6 +41,7 @@
class SceneInteractorTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
private val repository = utils.fakeSceneContainerRepository()
private val underTest = utils.sceneInteractor(repository = repository)
@@ -48,77 +51,156 @@
}
@Test
- fun currentScene() = runTest {
- val currentScene by collectLastValue(underTest.currentScene)
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+ fun changeScene() =
+ testScope.runTest {
+ val desiredScene by collectLastValue(underTest.desiredScene)
+ assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
- }
+ underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+ assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade))
+ }
@Test
- fun sceneTransitionProgress() = runTest {
- val transitionProgress by collectLastValue(underTest.transitionProgress)
- assertThat(transitionProgress).isEqualTo(1f)
+ fun onSceneChanged() =
+ testScope.runTest {
+ val desiredScene by collectLastValue(underTest.desiredScene)
+ assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- val progress = MutableStateFlow(0.55f)
- repository.setTransitionState(
- MutableStateFlow(
+ underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+ assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade))
+ }
+
+ @Test
+ fun transitionState() =
+ testScope.runTest {
+ val underTest = utils.fakeSceneContainerRepository()
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ underTest.setTransitionState(transitionState)
+ val reflectedTransitionState by collectLastValue(underTest.transitionState)
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+ val progress = MutableStateFlow(1f)
+ transitionState.value =
ObservableTransitionState.Transition(
fromScene = SceneKey.Lockscreen,
toScene = SceneKey.Shade,
progress = progress,
- ),
- )
- )
- assertThat(transitionProgress).isEqualTo(0.55f)
- }
-
- @Test
- fun isVisible() = runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- assertThat(isVisible).isTrue()
-
- underTest.setVisible(false, "reason")
- assertThat(isVisible).isFalse()
-
- underTest.setVisible(true, "reason")
- assertThat(isVisible).isTrue()
- }
-
- @Test
- fun sceneTransitions() = runTest {
- val transitions by collectLastValue(underTest.transitions)
- assertThat(transitions).isNull()
-
- val initialSceneKey = underTest.currentScene.value.key
- underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
- assertThat(transitions)
- .isEqualTo(
- SceneTransitionModel(
- from = initialSceneKey,
- to = SceneKey.Shade,
)
- )
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
- underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason")
- assertThat(transitions)
- .isEqualTo(
- SceneTransitionModel(
- from = SceneKey.Shade,
- to = SceneKey.QuickSettings,
+ progress.value = 0.1f
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+ progress.value = 0.9f
+ assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+ underTest.setTransitionState(null)
+ assertThat(reflectedTransitionState)
+ .isEqualTo(
+ ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
)
- )
- }
-
- @Test
- fun remoteUserInput() = runTest {
- val remoteUserInput by collectLastValue(underTest.remoteUserInput)
- assertThat(remoteUserInput).isNull()
-
- for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) {
- underTest.onRemoteUserInput(input)
- assertThat(remoteUserInput).isEqualTo(input)
}
- }
+
+ @Test
+ fun isVisible() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isTrue()
+
+ underTest.setVisible(false, "reason")
+ assertThat(isVisible).isFalse()
+
+ underTest.setVisible(true, "reason")
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
+ fun finishedSceneTransitions() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ underTest.setTransitionState(transitionState)
+ var transitionCount = 0
+ val job = launch {
+ underTest
+ .finishedSceneTransitions(
+ from = SceneKey.Shade,
+ to = SceneKey.QuickSettings,
+ )
+ .collect { transitionCount++ }
+ }
+
+ assertThat(transitionCount).isEqualTo(0)
+
+ underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = flowOf(0.5f),
+ )
+ runCurrent()
+ underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ runCurrent()
+ assertThat(transitionCount).isEqualTo(0)
+
+ underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.QuickSettings,
+ progress = flowOf(0.5f),
+ )
+ runCurrent()
+ underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
+ runCurrent()
+ assertThat(transitionCount).isEqualTo(1)
+
+ underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.QuickSettings,
+ toScene = SceneKey.Shade,
+ progress = flowOf(0.5f),
+ )
+ runCurrent()
+ underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ runCurrent()
+ assertThat(transitionCount).isEqualTo(1)
+
+ underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.QuickSettings,
+ progress = flowOf(0.5f),
+ )
+ runCurrent()
+ underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
+ runCurrent()
+ assertThat(transitionCount).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun remoteUserInput() =
+ testScope.runTest {
+ val remoteUserInput by collectLastValue(underTest.remoteUserInput)
+ assertThat(remoteUserInput).isNull()
+
+ for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) {
+ underTest.onRemoteUserInput(input)
+ assertThat(remoteUserInput).isEqualTo(input)
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index bec0b77..45db7a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -29,15 +29,17 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.model.SysUiState
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -77,59 +79,86 @@
sceneLogger = mock(),
)
- @Before
- fun setUp() {
- prepareState()
- }
-
@Test
fun hydrateVisibility_featureEnabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentDesiredSceneKey by
+ collectLastValue(sceneInteractor.desiredScene.map { it.key })
val isVisible by collectLastValue(sceneInteractor.isVisible)
- prepareState(
- isFeatureEnabled = true,
- isDeviceUnlocked = true,
- initialSceneKey = SceneKey.Gone,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ val transitionStateFlow =
+ prepareState(
+ isFeatureEnabled = true,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
assertThat(isVisible).isTrue()
underTest.start()
-
assertThat(isVisible).isFalse()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Gone,
+ toScene = SceneKey.Shade,
+ progress = flowOf(0.5f),
+ )
assertThat(isVisible).isTrue()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+ transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ assertThat(isVisible).isTrue()
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.Gone,
+ progress = flowOf(0.5f),
+ )
+ assertThat(isVisible).isTrue()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+ transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+ assertThat(isVisible).isFalse()
}
@Test
fun hydrateVisibility_featureDisabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentDesiredSceneKey by
+ collectLastValue(sceneInteractor.desiredScene.map { it.key })
val isVisible by collectLastValue(sceneInteractor.isVisible)
- prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = true,
- initialSceneKey = SceneKey.Lockscreen,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ val transitionStateFlow =
+ prepareState(
+ isFeatureEnabled = false,
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
assertThat(isVisible).isTrue()
underTest.start()
+
assertThat(isVisible).isTrue()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
+ sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Gone,
+ toScene = SceneKey.Shade,
+ progress = flowOf(0.5f),
+ )
assertThat(isVisible).isTrue()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+ transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
assertThat(isVisible).isTrue()
}
@Test
fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = true,
@@ -146,7 +175,7 @@
@Test
fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = false,
@@ -163,7 +192,7 @@
@Test
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = false,
@@ -180,7 +209,7 @@
@Test
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = false,
@@ -197,7 +226,7 @@
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isBypassEnabled = true,
@@ -214,7 +243,7 @@
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isBypassEnabled = false,
@@ -231,7 +260,7 @@
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isBypassEnabled = true,
@@ -248,7 +277,7 @@
@Test
fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = false,
@@ -265,7 +294,7 @@
@Test
fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = false,
@@ -282,6 +311,7 @@
@Test
fun hydrateSystemUiState() =
testScope.runTest {
+ val transitionStateFlow = prepareState()
underTest.start()
runCurrent()
clearInvocations(sysUiState)
@@ -294,9 +324,16 @@
SceneKey.QuickSettings,
)
.forEachIndexed { index, sceneKey ->
- sceneInteractor.setCurrentScene(SceneModel(sceneKey), "reason")
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
runCurrent()
+ verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+ sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason")
+ runCurrent()
+ verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+
+ transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
+ runCurrent()
verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
}
}
@@ -304,7 +341,7 @@
@Test
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
@@ -321,7 +358,7 @@
@Test
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
@@ -338,7 +375,7 @@
@Test
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() =
testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
isFeatureEnabled = false,
initialSceneKey = SceneKey.Lockscreen,
@@ -358,17 +395,27 @@
isBypassEnabled: Boolean = false,
initialSceneKey: SceneKey? = null,
authenticationMethod: AuthenticationMethodModel? = null,
- ) {
+ ): MutableStateFlow<ObservableTransitionState> {
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
authenticationRepository.setUnlocked(isDeviceUnlocked)
keyguardRepository.setBypassEnabled(isBypassEnabled)
- initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") }
+ val transitionStateFlow =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ sceneInteractor.setTransitionState(transitionStateFlow)
+ initialSceneKey?.let {
+ transitionStateFlow.value = ObservableTransitionState.Idle(it)
+ sceneInteractor.changeScene(SceneModel(it), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(it), "reason")
+ }
authenticationMethod?.let {
authenticationRepository.setAuthenticationMethod(authenticationMethod)
authenticationRepository.setLockscreenEnabled(
authenticationMethod != AuthenticationMethodModel.None
)
}
+ return transitionStateFlow
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 9f3b12b..da6c4269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -69,7 +69,8 @@
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+ underTest.onSceneChanged(SceneModel(SceneKey.Shade))
+
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 77e4d89..2d3ee0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -34,7 +34,7 @@
class ActionIntentCreatorTest : SysuiTestCase() {
@Test
- fun testCreateShareIntent() {
+ fun testCreateShare() {
val uri = Uri.parse("content://fake")
val output = ActionIntentCreator.createShare(uri)
@@ -59,7 +59,17 @@
}
@Test
- fun testCreateShareIntentWithSubject() {
+ fun testCreateShare_embeddedUserIdRemoved() {
+ val uri = Uri.parse("content://555@fake")
+
+ val output = ActionIntentCreator.createShare(uri)
+
+ assertThat(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+ .hasData(Uri.parse("content://fake"))
+ }
+
+ @Test
+ fun testCreateShareWithSubject() {
val uri = Uri.parse("content://fake")
val subject = "Example subject"
@@ -83,7 +93,7 @@
}
@Test
- fun testCreateShareIntentWithExtraText() {
+ fun testCreateShareWithText() {
val uri = Uri.parse("content://fake")
val extraText = "Extra text"
@@ -107,13 +117,13 @@
}
@Test
- fun testCreateEditIntent() {
+ fun testCreateEdit() {
val uri = Uri.parse("content://fake")
val context = mock<Context>()
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
- val output = ActionIntentCreator.createEditIntent(uri, context)
+ val output = ActionIntentCreator.createEdit(uri, context)
assertThat(output).hasAction(Intent.ACTION_EDIT)
assertThat(output).hasData(uri)
@@ -129,7 +139,18 @@
}
@Test
- fun testCreateEditIntent_withEditor() {
+ fun testCreateEdit_embeddedUserIdRemoved() {
+ val uri = Uri.parse("content://555@fake")
+ val context = mock<Context>()
+ whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
+
+ val output = ActionIntentCreator.createEdit(uri, context)
+
+ assertThat(output).hasData(Uri.parse("content://fake"))
+ }
+
+ @Test
+ fun testCreateEdit_withEditor() {
val uri = Uri.parse("content://fake")
val context = mock<Context>()
val component = ComponentName("com.android.foo", "com.android.foo.Something")
@@ -137,7 +158,7 @@
whenever(context.getString(eq(R.string.config_screenshotEditor)))
.thenReturn(component.flattenToString())
- val output = ActionIntentCreator.createEditIntent(uri, context)
+ val output = ActionIntentCreator.createEdit(uri, context)
assertThat(output).hasComponent(component)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index d930160..7443097 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -78,7 +78,7 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -91,7 +91,7 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 764005b8..0cc0b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -17,6 +17,10 @@
package com.android.systemui.statusbar.notification.row
+import android.app.Notification
+import android.net.Uri
+import android.os.UserHandle
+import android.os.UserHandle.USER_ALL
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -29,13 +33,17 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -46,9 +54,9 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.SystemClock
import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
import junit.framework.Assert
import org.junit.After
import org.junit.Before
@@ -56,9 +64,11 @@
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
+import java.util.Optional
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -94,10 +104,10 @@
private val featureFlags: FeatureFlags = mock()
private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
private val bubblesManager: BubblesManager = mock()
+ private val settingsController: NotificationSettingsController = mock()
private val dragController: ExpandableNotificationRowDragController = mock()
private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
private val statusBarService: IStatusBarService = mock()
-
private lateinit var controller: ExpandableNotificationRowController
@Before
@@ -134,11 +144,16 @@
featureFlags,
peopleNotificationIdentifier,
Optional.of(bubblesManager),
+ settingsController,
dragController,
dismissibilityProvider,
statusBarService
)
whenever(view.childrenContainer).thenReturn(childrenContainer)
+
+ val notification = Notification.Builder(mContext).build()
+ val sbn = SbnBuilder().setNotification(notification).build()
+ whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build())
}
@After
@@ -206,4 +221,74 @@
verify(view).removeChildNotification(eq(childView))
verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
}
+
+ @Test
+ fun registerSettingsListener_forBubbles() {
+ controller.init(mock(NotificationEntry::class.java))
+ val viewStateObserver = withArgCaptor {
+ verify(view).addOnAttachStateChangeListener(capture());
+ }
+ viewStateObserver.onViewAttachedToWindow(view);
+ verify(settingsController).addCallback(any(), any());
+ }
+
+ @Test
+ fun unregisterSettingsListener_forBubbles() {
+ controller.init(mock(NotificationEntry::class.java))
+ val viewStateObserver = withArgCaptor {
+ verify(view).addOnAttachStateChangeListener(capture());
+ }
+ viewStateObserver.onViewDetachedFromWindow(view);
+ verify(settingsController).removeCallback(any(), any());
+ }
+
+ @Test
+ fun settingsListener_invalidUri() {
+ controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1")
+
+ verify(view, never()).getPrivateLayout()
+ }
+
+ @Test
+ fun settingsListener_invalidUserId() {
+ controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1")
+ controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null)
+
+ verify(view, never()).getPrivateLayout()
+ }
+
+ @Test
+ fun settingsListener_validUserId() {
+ val childView: NotificationContentView = mock()
+ whenever(view.privateLayout).thenReturn(childView)
+
+ controller.mSettingsListener.onSettingChanged(
+ BUBBLES_SETTING_URI, view.entry.sbn.userId, "1")
+ verify(childView).setBubblesEnabledForUser(true)
+
+ controller.mSettingsListener.onSettingChanged(
+ BUBBLES_SETTING_URI, view.entry.sbn.userId, "9")
+ verify(childView).setBubblesEnabledForUser(false)
+ }
+
+ @Test
+ fun settingsListener_userAll() {
+ val childView: NotificationContentView = mock()
+ whenever(view.privateLayout).thenReturn(childView)
+
+ val notification = Notification.Builder(mContext).build()
+ val sbn = SbnBuilder().setNotification(notification)
+ .setUser(UserHandle.of(USER_ALL))
+ .build()
+ whenever(view.entry).thenReturn(NotificationEntryBuilder()
+ .setSbn(sbn)
+ .setUser(UserHandle.of(USER_ALL))
+ .build())
+
+ controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
+ verify(childView).setBubblesEnabledForUser(true)
+
+ controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0")
+ verify(childView).setBubblesEnabledForUser(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 0b90ebe..c4baa69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -250,6 +250,9 @@
.thenReturn(actionListMarginTarget)
view.setContainingNotification(mockContainingNotification)
+ // Given: controller says bubbles are enabled for the user
+ view.setBubblesEnabledForUser(true);
+
// When: call NotificationContentView.setExpandedChild() to set the expandedChild
view.expandedChild = mockExpandedChild
@@ -305,6 +308,12 @@
// NotificationEntry, which should show bubble button
view.onNotificationUpdated(createMockNotificationEntry(true))
+ // Then: no bubble yet
+ assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+ // Given: controller says bubbles are enabled for the user
+ view.setBubblesEnabledForUser(true);
+
// Then: bottom margin of actionListMarginTarget should not change, still be 20
assertEquals(0, getMarginBottom(actionListMarginTarget))
}
@@ -405,7 +414,6 @@
val userMock: UserHandle = mock()
whenever(this.sbn).thenReturn(sbnMock)
whenever(sbnMock.user).thenReturn(userMock)
- doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
}
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 90adabf..596e9a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -62,6 +62,7 @@
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.UserHandle;
+import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -132,6 +133,8 @@
@Mock
private PackageManager mMockPackageManager;
@Mock
+ private UserManager mUserManager;
+ @Mock
private OnUserInteractionCallback mOnUserInteractionCallback;
@Mock
private BubblesManager mBubblesManager;
@@ -238,6 +241,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -262,6 +266,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -314,6 +319,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -339,6 +345,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -363,6 +370,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -398,6 +406,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -423,6 +432,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -452,6 +462,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -476,6 +487,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -504,6 +516,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -532,6 +545,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -563,6 +577,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -600,6 +615,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -628,6 +644,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -663,6 +680,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -691,6 +709,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -735,6 +754,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -778,6 +798,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -822,6 +843,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -860,6 +882,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -896,6 +919,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -936,6 +960,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -967,6 +992,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -996,6 +1022,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1033,6 +1060,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1069,6 +1097,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1104,6 +1133,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1143,6 +1173,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1173,6 +1204,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1198,6 +1230,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1219,11 +1252,13 @@
@Test
public void testSelectPriorityRequestsPinPeopleTile() {
+ when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(true);
//WHEN channel is default importance
mNotificationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1250,10 +1285,45 @@
}
@Test
+ public void testSelectPriorityRequestsPinPeopleTile_noMultiuser() {
+ when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(false);
+ //WHEN channel is default importance
+ mNotificationChannel.setImportantConversation(false);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mMockPackageManager,
+ mUserManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ mIconFactory,
+ mContext,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+
+ // WHEN user clicks "priority"
+ mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
+
+ // and then done
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ // No widget prompt; on a secondary user
+ verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(any(), any());
+ }
+
+ @Test
public void testSelectDefaultDoesNotRequestPinPeopleTile() {
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
@@ -1288,6 +1358,7 @@
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
+ mUserManager,
mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 3cefc99..705d52b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -52,6 +52,7 @@
import android.graphics.Color;
import android.os.Binder;
import android.os.Handler;
+import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
@@ -137,6 +138,8 @@
@Mock private HeadsUpManagerPhone mHeadsUpManagerPhone;
@Mock private ActivityStarter mActivityStarter;
+ @Mock private UserManager mUserManager;
+
@Before
public void setUp() {
mTestableLooper = TestableLooper.get(this);
@@ -147,7 +150,7 @@
mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
mAccessibilityManager,
- mHighPriorityProvider, mINotificationManager,
+ mHighPriorityProvider, mINotificationManager, mUserManager,
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
new file mode 100644
index 0000000..614995b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2023 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.notification.row
+
+import android.app.ActivityManager
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.provider.Settings.Secure
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.row.NotificationSettingsController.Listener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.SecureSettings
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationSettingsControllerTest : SysuiTestCase() {
+
+ val setting1: String = Secure.NOTIFICATION_BUBBLES
+ val setting2: String = Secure.ACCESSIBILITY_ENABLED
+ val settingUri1: Uri = Secure.getUriFor(setting1)
+ val settingUri2: Uri = Secure.getUriFor(setting2)
+
+ @Mock
+ private lateinit var userTracker: UserTracker
+ private lateinit var mainHandler: Handler
+ private lateinit var backgroundHandler: Handler
+ private lateinit var testableLooper: TestableLooper
+ @Mock
+ private lateinit var secureSettings: SecureSettings
+ @Mock
+ private lateinit var dumpManager: DumpManager
+
+ @Captor
+ private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
+ @Captor
+ private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ private lateinit var controller: NotificationSettingsController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ mainHandler = Handler(testableLooper.looper)
+ backgroundHandler = Handler(testableLooper.looper)
+ allowTestableLooperAsMainThread()
+ controller =
+ NotificationSettingsController(
+ userTracker,
+ mainHandler,
+ backgroundHandler,
+ secureSettings,
+ dumpManager
+ )
+ }
+
+ @After
+ fun tearDown() {
+ disallowTestableLooperAsMainThread()
+ }
+
+ @Test
+ fun creationRegistersCallbacks() {
+ verify(userTracker).addCallback(any(), any())
+ verify(dumpManager).registerNormalDumpable(anyString(), eq(controller))
+ }
+ @Test
+ fun updateContentObserverRegistration_onUserChange_noSettingsListeners() {
+ verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+ val userCallback = userTrackerCallbackCaptor.value
+ val userId = 9
+
+ // When: User is changed
+ userCallback.onUserChanged(userId, context)
+
+ // Validate: Nothing to do, since we aren't monitoring settings
+ verify(secureSettings, never()).unregisterContentObserver(any())
+ verify(secureSettings, never()).registerContentObserverForUser(
+ any(Uri::class.java), anyBoolean(), any(), anyInt())
+ }
+ @Test
+ fun updateContentObserverRegistration_onUserChange_withSettingsListeners() {
+ // When: someone is listening to a setting
+ controller.addCallback(settingUri1,
+ Mockito.mock(Listener::class.java))
+
+ verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+ val userCallback = userTrackerCallbackCaptor.value
+ val userId = 9
+
+ // Then: User is changed
+ userCallback.onUserChanged(userId, context)
+
+ // Validate: The tracker is unregistered and re-registered with the new user
+ verify(secureSettings).unregisterContentObserver(any())
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri1), eq(false), any(), eq(userId))
+ }
+
+ @Test
+ fun addCallback_onlyFirstForUriRegistersObserver() {
+ controller.addCallback(settingUri1,
+ Mockito.mock(Listener::class.java))
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+ controller.addCallback(settingUri1,
+ Mockito.mock(Listener::class.java))
+ verify(secureSettings).registerContentObserverForUser(
+ any(Uri::class.java), anyBoolean(), any(), anyInt())
+ }
+
+ @Test
+ fun addCallback_secondUriRegistersObserver() {
+ controller.addCallback(settingUri1,
+ Mockito.mock(Listener::class.java))
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+ controller.addCallback(settingUri2,
+ Mockito.mock(Listener::class.java))
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri1), anyBoolean(), any(), anyInt())
+ }
+
+ @Test
+ fun removeCallback_lastUnregistersObserver() {
+ val listenerSetting1 : Listener = mock()
+ val listenerSetting2 : Listener = mock()
+ controller.addCallback(settingUri1, listenerSetting1)
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+ controller.addCallback(settingUri2, listenerSetting2)
+ verify(secureSettings).registerContentObserverForUser(
+ eq(settingUri2), anyBoolean(), any(), anyInt())
+
+ controller.removeCallback(settingUri2, listenerSetting2)
+ verify(secureSettings, never()).unregisterContentObserver(any())
+
+ controller.removeCallback(settingUri1, listenerSetting1)
+ verify(secureSettings).unregisterContentObserver(any())
+ }
+
+ @Test
+ fun addCallback_updatesCurrentValue() {
+ whenever(secureSettings.getStringForUser(
+ setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+ whenever(secureSettings.getStringForUser(
+ setting2, ActivityManager.getCurrentUser())).thenReturn("5")
+
+ val listenerSetting1a : Listener = mock()
+ val listenerSetting1b : Listener = mock()
+ val listenerSetting2 : Listener = mock()
+
+ controller.addCallback(settingUri1, listenerSetting1a)
+ controller.addCallback(settingUri1, listenerSetting1b)
+ controller.addCallback(settingUri2, listenerSetting2)
+
+ testableLooper.processAllMessages()
+
+ verify(listenerSetting1a).onSettingChanged(
+ settingUri1, ActivityManager.getCurrentUser(), "9")
+ verify(listenerSetting1b).onSettingChanged(
+ settingUri1, ActivityManager.getCurrentUser(), "9")
+ verify(listenerSetting2).onSettingChanged(
+ settingUri2, ActivityManager.getCurrentUser(), "5")
+ }
+
+ @Test
+ fun removeCallback_noMoreUpdates() {
+ whenever(secureSettings.getStringForUser(
+ setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+
+ val listenerSetting1a : Listener = mock()
+ val listenerSetting1b : Listener = mock()
+
+ // First, register
+ controller.addCallback(settingUri1, listenerSetting1a)
+ controller.addCallback(settingUri1, listenerSetting1b)
+ testableLooper.processAllMessages()
+
+ verify(secureSettings).registerContentObserverForUser(
+ any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt())
+ verify(listenerSetting1a).onSettingChanged(
+ settingUri1, ActivityManager.getCurrentUser(), "9")
+ verify(listenerSetting1b).onSettingChanged(
+ settingUri1, ActivityManager.getCurrentUser(), "9")
+ Mockito.clearInvocations(listenerSetting1b)
+ Mockito.clearInvocations(listenerSetting1a)
+
+ // Remove one of them
+ controller.removeCallback(settingUri1, listenerSetting1a)
+
+ // On update, only remaining listener should get the callback
+ settingsObserverCaptor.value.onChange(false, settingUri1)
+ testableLooper.processAllMessages()
+
+ verify(listenerSetting1a, never()).onSettingChanged(
+ settingUri1, ActivityManager.getCurrentUser(), "9")
+ verify(listenerSetting1b).onSettingChanged(
+ settingUri1, ActivityManager.getCurrentUser(), "9")
+ }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 1bf431b..1c8dac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index fef042b..2dbeb7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -489,6 +489,26 @@
}
@Test
+ fun wifiNetwork_neverHasHotspot() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+ val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
+ }
+
+ @Test
fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index 7002cbb..9959e00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -18,13 +18,18 @@
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.concurrency.FakeExecutor
@@ -34,8 +39,13 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,6 +55,7 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.verify
/**
* Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
@@ -57,10 +68,25 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
- private lateinit var underTest: WifiRepositoryViaTrackerLib
+ // Using lazy means that the class will only be constructed once it's fetched. Because the
+ // repository internally sets some values on construction, we need to set up some test
+ // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
+ // inside each test case without needing to manually recreate the repository.
+ private val underTest: WifiRepositoryViaTrackerLib by lazy {
+ WifiRepositoryViaTrackerLib(
+ featureFlags,
+ testScope.backgroundScope,
+ executor,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ logger,
+ tableLogger,
+ )
+ }
private val executor = FakeExecutor(FakeSystemClock())
private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock())
+ private val featureFlags = FakeFeatureFlags()
private val tableLogger = mock<TableLogBuffer>()
private val wifiManager =
mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) }
@@ -74,12 +100,21 @@
@Before
fun setUp() {
+ featureFlags.set(Flags.INSTANT_TETHER, false)
whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor)))
.thenReturn(wifiPickerTracker)
- underTest = createRepo()
}
@Test
+ fun wifiPickerTrackerCreation_scansDisabled() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+ testScope.runCurrent()
+
+ verify(wifiPickerTracker).disableScanning()
+ }
+
+ @Test
fun isWifiEnabled_enabled_true() =
testScope.runTest {
val latest by collectLastValue(underTest.isWifiEnabled)
@@ -238,7 +273,7 @@
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
- whenever(this.ssid).thenReturn(SSID)
+ whenever(this.title).thenReturn(TITLE)
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -246,7 +281,240 @@
assertThat(latest is WifiNetworkModel.Active).isTrue()
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.level).isEqualTo(3)
- assertThat(latestActive.ssid).isEqualTo(SSID)
+ assertThat(latestActive.ssid).isEqualTo(TITLE)
+ }
+
+ @Test
+ fun accessPointInfo_alwaysFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.title).thenReturn(TITLE)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.isPasspointAccessPoint).isFalse()
+ assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse()
+ assertThat(latestActive.passpointProviderFriendlyName).isNull()
+ }
+
+ @Test
+ fun wifiNetwork_unreachableLevel_inactiveNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+ }
+
+ @Test
+ fun wifiNetwork_levelTooHigh_inactiveNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(WIFI_LEVEL_MAX + 1)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+ }
+
+ @Test
+ fun wifiNetwork_levelTooLow_inactiveNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(WIFI_LEVEL_MIN - 1)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+ }
+
+ @Test
+ fun wifiNetwork_levelIsMax_activeNetworkWithMaxLevel() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(WIFI_LEVEL_MAX)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java)
+ assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MAX)
+ }
+
+ @Test
+ fun wifiNetwork_levelIsMin_activeNetworkWithMinLevel() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(WIFI_LEVEL_MIN)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java)
+ assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MIN)
+ }
+
+ @Test
+ fun wifiNetwork_notHotspot_none() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_unknown() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_phone() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.PHONE)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_tablet() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.TABLET)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_laptop() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_watch() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.WATCH)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_auto() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.AUTO)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_invalid() =
+ testScope.runTest {
+ featureFlags.set(Flags.INSTANT_TETHER, true)
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(1234)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.INVALID)
+ }
+
+ @Test
+ fun wifiNetwork_hotspot_flagOff_valueNotUsed() =
+ testScope.runTest {
+ // WHEN the flag is off
+ featureFlags.set(Flags.INSTANT_TETHER, false)
+
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ // THEN NONE is always used, even if the wifi entry does have a hotspot device type
+ assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+ .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
}
@Test
@@ -258,6 +526,7 @@
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
+ whenever(this.subscriptionId).thenReturn(567)
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -265,7 +534,7 @@
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
val latestMerged = latest as WifiNetworkModel.CarrierMerged
assertThat(latestMerged.level).isEqualTo(3)
- // numberOfLevels = maxSignalLevel + 1
+ assertThat(latestMerged.subscriptionId).isEqualTo(567)
}
@Test
@@ -288,30 +557,23 @@
assertThat(latestMerged.numberOfLevels).isEqualTo(6)
}
- /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID.
@Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
}
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
+ getCallback().onWifiEntriesChanged()
assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
}
- */
-
@Test
fun wifiNetwork_notValidated_networkNotValidated() =
testScope.runTest {
@@ -382,7 +644,7 @@
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
- whenever(this.ssid).thenReturn("AB")
+ whenever(this.title).thenReturn("AB")
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -397,7 +659,7 @@
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(4)
- whenever(this.ssid).thenReturn("CD")
+ whenever(this.title).thenReturn("CD")
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry)
getCallback().onWifiEntriesChanged()
@@ -430,12 +692,12 @@
val wifiEntry =
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
- whenever(this.ssid).thenReturn(SSID)
+ whenever(this.title).thenReturn(TITLE)
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID)
+ assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(TITLE)
// WHEN we lose our current network
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
@@ -480,7 +742,7 @@
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(1)
- whenever(this.ssid).thenReturn(SSID)
+ whenever(this.title).thenReturn(TITLE)
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -488,7 +750,7 @@
assertThat(latest1 is WifiNetworkModel.Active).isTrue()
val latest1Active = latest1 as WifiNetworkModel.Active
assertThat(latest1Active.level).isEqualTo(1)
- assertThat(latest1Active.ssid).isEqualTo(SSID)
+ assertThat(latest1Active.ssid).isEqualTo(TITLE)
// WHEN we add a second subscriber after having already emitted a value
val latest2 by collectLastValue(underTest.wifiNetwork)
@@ -497,7 +759,7 @@
assertThat(latest2 is WifiNetworkModel.Active).isTrue()
val latest2Active = latest2 as WifiNetworkModel.Active
assertThat(latest2Active.level).isEqualTo(1)
- assertThat(latest2Active.ssid).isEqualTo(SSID)
+ assertThat(latest2Active.ssid).isEqualTo(TITLE)
}
@Test
@@ -541,40 +803,15 @@
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
- /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID.
- @Test
- fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- */
-
@Test
- fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
+ fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
testScope.runTest {
collectLastValue(underTest.wifiNetwork)
val wifiEntry =
- mock<WifiEntry>().apply {
+ mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
- whenever(this.ssid).thenReturn(null)
+ whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -584,14 +821,14 @@
}
@Test
- fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
+ fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() =
testScope.runTest {
collectLastValue(underTest.wifiNetwork)
val wifiEntry =
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
- whenever(this.ssid).thenReturn(UNKNOWN_SSID)
+ whenever(this.title).thenReturn(null)
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -601,14 +838,31 @@
}
@Test
- fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
+ fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() =
testScope.runTest {
collectLastValue(underTest.wifiNetwork)
val wifiEntry =
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
- whenever(this.ssid).thenReturn("fakeSsid")
+ whenever(this.title).thenReturn(UNKNOWN_SSID)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.title).thenReturn("fakeSsid")
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -626,7 +880,7 @@
val wifiEntry =
mock<WifiEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
- whenever(this.ssid).thenReturn("fakeSsid")
+ whenever(this.title).thenReturn("fakeSsid")
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
@@ -643,23 +897,74 @@
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
+ @Test
+ fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiActivity)
+
+ getTrafficStateCallback()
+ .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE)
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiActivity)
+
+ getTrafficStateCallback()
+ .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN)
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiActivity)
+
+ getTrafficStateCallback()
+ .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT)
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiActivity)
+
+ getTrafficStateCallback()
+ .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT)
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+ }
+
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
testScope.runCurrent()
return callbackCaptor.value
}
- private fun createRepo(): WifiRepositoryViaTrackerLib {
- return WifiRepositoryViaTrackerLib(
- testScope.backgroundScope,
- executor,
- wifiPickerTrackerFactory,
- wifiManager,
- logger,
- tableLogger,
- )
+ private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>()
+ verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry {
+ return mock<HotspotNetworkEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.deviceType).thenReturn(type)
+ }
}
private companion object {
- const val SSID = "AB"
+ const val TITLE = "AB"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index 4e0c309..ba035be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -136,7 +136,8 @@
networkId = 5,
isValidated = true,
level = 3,
- ssid = "Test SSID"
+ ssid = "Test SSID",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
)
activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
@@ -146,6 +147,7 @@
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP"))
}
@Test
fun logDiffs_activeToInactive_resetsAllActiveFields() {
@@ -165,6 +167,7 @@
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "null"))
}
@Test
@@ -175,7 +178,8 @@
networkId = 5,
isValidated = true,
level = 3,
- ssid = "Test SSID"
+ ssid = "Test SSID",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO,
)
val prevVal =
WifiNetworkModel.CarrierMerged(
@@ -191,6 +195,7 @@
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO"))
}
@Test
fun logDiffs_activeToCarrierMerged_logsAllFields() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index c886f9b..cdeb592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -29,6 +29,9 @@
import static org.mockito.Mockito.when;
import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.PowerManager;
@@ -56,6 +59,9 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -65,8 +71,10 @@
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private DemoModeController mDemoModeController;
@Mock private View mView;
+ @Mock private UsbPort mUsbPort;
+ @Mock private UsbManager mUsbManager;
+ @Mock private UsbPortStatus mUsbPortStatus;
private BatteryControllerImpl mBatteryController;
-
private MockitoSession mMockitoSession;
@Before
@@ -255,4 +263,38 @@
Assert.assertFalse(mBatteryController.isBatteryDefender());
}
+
+ @Test
+ public void complianceChanged_complianceIncompatible_outputsTrue() {
+ mContext.addMockSystemService(UsbManager.class, mUsbManager);
+ setupIncompatibleCharging();
+ Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isIncompatibleCharging());
+ }
+
+ @Test
+ public void complianceChanged_emptyComplianceWarnings_outputsFalse() {
+ mContext.addMockSystemService(UsbManager.class, mUsbManager);
+ setupIncompatibleCharging();
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+ Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isIncompatibleCharging());
+ }
+
+ private void setupIncompatibleCharging() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+ when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
+ when(mUsbPortStatus.isConnected()).thenReturn(true);
+ when(mUsbPortStatus.getComplianceWarnings())
+ .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_OTHER});
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
index 2662da2..1404a4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -16,43 +16,128 @@
package com.android.systemui.util
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class ListenerSetTest : SysuiTestCase() {
+open class ListenerSetTest : SysuiTestCase() {
- var runnableSet: ListenerSet<Runnable> = ListenerSet()
+ private val runnableSet: IListenerSet<Runnable> = makeRunnableListenerSet()
- @Before
- fun setup() {
- runnableSet = ListenerSet()
- }
+ open fun makeRunnableListenerSet(): IListenerSet<Runnable> = ListenerSet()
@Test
fun addIfAbsent_doesNotDoubleAdd() {
// setup & preconditions
val runnable1 = Runnable { }
val runnable2 = Runnable { }
- assertThat(runnableSet.toList()).isEmpty()
+ assertThat(runnableSet).isEmpty()
// Test that an element can be added
assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
- assertThat(runnableSet.toList()).containsExactly(runnable1)
+ assertThat(runnableSet).containsExactly(runnable1)
// Test that a second element can be added
assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
- assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
// Test that re-adding the first element does nothing and returns false
assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
- assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
+ }
+
+ @Test
+ fun isEmpty_changes() {
+ val runnable = Runnable { }
+ assertThat(runnableSet).isEmpty()
+ assertThat(runnableSet.isEmpty()).isTrue()
+ assertThat(runnableSet.isNotEmpty()).isFalse()
+
+ assertThat(runnableSet.addIfAbsent(runnable)).isTrue()
+ assertThat(runnableSet).isNotEmpty()
+ assertThat(runnableSet.isEmpty()).isFalse()
+ assertThat(runnableSet.isNotEmpty()).isTrue()
+
+ assertThat(runnableSet.remove(runnable)).isTrue()
+ assertThat(runnableSet).isEmpty()
+ assertThat(runnableSet.isEmpty()).isTrue()
+ assertThat(runnableSet.isNotEmpty()).isFalse()
+ }
+
+ @Test
+ fun size_changes() {
+ assertThat(runnableSet).isEmpty()
+ assertThat(runnableSet.size).isEqualTo(0)
+
+ assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+ assertThat(runnableSet.size).isEqualTo(1)
+
+ assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+ assertThat(runnableSet.size).isEqualTo(2)
+ }
+
+ @Test
+ fun contains_worksAsExpected() {
+ val runnable1 = Runnable { }
+ val runnable2 = Runnable { }
+ assertThat(runnableSet).isEmpty()
+ assertThat(runnable1 in runnableSet).isFalse()
+ assertThat(runnable2 in runnableSet).isFalse()
+ assertThat(runnableSet).doesNotContain(runnable1)
+ assertThat(runnableSet).doesNotContain(runnable2)
+
+ assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+ assertThat(runnable1 in runnableSet).isTrue()
+ assertThat(runnable2 in runnableSet).isFalse()
+ assertThat(runnableSet).contains(runnable1)
+ assertThat(runnableSet).doesNotContain(runnable2)
+
+ assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+ assertThat(runnable1 in runnableSet).isTrue()
+ assertThat(runnable2 in runnableSet).isTrue()
+ assertThat(runnableSet).contains(runnable1)
+ assertThat(runnableSet).contains(runnable2)
+
+ assertThat(runnableSet.remove(runnable1)).isTrue()
+ assertThat(runnable1 in runnableSet).isFalse()
+ assertThat(runnable2 in runnableSet).isTrue()
+ assertThat(runnableSet).doesNotContain(runnable1)
+ assertThat(runnableSet).contains(runnable2)
+ }
+
+ @Test
+ fun containsAll_worksAsExpected() {
+ val runnable1 = Runnable { }
+ val runnable2 = Runnable { }
+
+ assertThat(runnableSet).isEmpty()
+ assertThat(runnableSet.containsAll(listOf())).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse()
+ assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse()
+ assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
+
+ assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+ assertThat(runnableSet.containsAll(listOf())).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse()
+ assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
+
+ assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+ assertThat(runnableSet.containsAll(listOf())).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isTrue()
+
+ assertThat(runnableSet.remove(runnable1)).isTrue()
+ assertThat(runnableSet.containsAll(listOf())).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse()
+ assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue()
+ assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
}
@Test
@@ -60,22 +145,22 @@
// setup and preconditions
val runnable1 = Runnable { }
val runnable2 = Runnable { }
- assertThat(runnableSet.toList()).isEmpty()
+ assertThat(runnableSet).isEmpty()
runnableSet.addIfAbsent(runnable1)
runnableSet.addIfAbsent(runnable2)
- assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
// Test that removing the first runnable only removes that one runnable
assertThat(runnableSet.remove(runnable1)).isTrue()
- assertThat(runnableSet.toList()).containsExactly(runnable2)
+ assertThat(runnableSet).containsExactly(runnable2)
// Test that removing a non-present runnable does not error
assertThat(runnableSet.remove(runnable1)).isFalse()
- assertThat(runnableSet.toList()).containsExactly(runnable2)
+ assertThat(runnableSet).containsExactly(runnable2)
// Test that removing the other runnable succeeds
assertThat(runnableSet.remove(runnable2)).isTrue()
- assertThat(runnableSet.toList()).isEmpty()
+ assertThat(runnableSet).isEmpty()
}
@Test
@@ -92,17 +177,17 @@
val runnable2 = Runnable {
runnablesCalled.add(2)
}
- assertThat(runnableSet.toList()).isEmpty()
+ assertThat(runnableSet).isEmpty()
runnableSet.addIfAbsent(runnable1)
runnableSet.addIfAbsent(runnable2)
- assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
// Test that both runnables are called and 1 was removed
for (runnable in runnableSet) {
runnable.run()
}
assertThat(runnablesCalled).containsExactly(1, 2)
- assertThat(runnableSet.toList()).containsExactly(runnable2)
+ assertThat(runnableSet).containsExactly(runnable2)
}
@Test
@@ -120,16 +205,16 @@
val runnable2 = Runnable {
runnablesCalled.add(2)
}
- assertThat(runnableSet.toList()).isEmpty()
+ assertThat(runnableSet).isEmpty()
runnableSet.addIfAbsent(runnable1)
runnableSet.addIfAbsent(runnable2)
- assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
// Test that both original runnables are called and 99 was added but not called
for (runnable in runnableSet) {
runnable.run()
}
assertThat(runnablesCalled).containsExactly(1, 2)
- assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99)
+ assertThat(runnableSet).containsExactly(runnable1, runnable2, runnable99)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
new file mode 100644
index 0000000..c89e317
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 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.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NamedListenerSetTest : ListenerSetTest() {
+ override fun makeRunnableListenerSet(): IListenerSet<Runnable> = NamedListenerSet()
+
+ private val runnableSet = NamedListenerSet(NamedRunnable::name)
+
+ class NamedRunnable(val name: String, private val block: () -> Unit = {}) : Runnable {
+ override fun run() = block()
+ }
+
+ @Test
+ fun addIfAbsent_addsMultipleWithSameName_onlyIfInstanceIsAbsent() {
+ // setup & preconditions
+ val runnable1 = NamedRunnable("A")
+ val runnable2 = NamedRunnable("A")
+ assertThat(runnableSet).isEmpty()
+
+ // Test that an element can be added
+ assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+ assertThat(runnableSet).containsExactly(runnable1)
+
+ // Test that a second element can be added, even with the same name
+ assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
+
+ // Test that re-adding the first element does nothing and returns false
+ assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
+ assertThat(runnableSet).containsExactly(runnable1, runnable2)
+ }
+
+ @Test
+ fun forEachNamed_includesCorrectNames() {
+ val runnable1 = NamedRunnable("A")
+ val runnable2 = NamedRunnable("X")
+ val runnable3 = NamedRunnable("X")
+ assertThat(runnableSet).isEmpty()
+
+ assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+ assertThat(runnableSet.toNamedPairs())
+ .containsExactly(
+ "A" to runnable1,
+ )
+
+ assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+ assertThat(runnableSet.toNamedPairs())
+ .containsExactly(
+ "A" to runnable1,
+ "X" to runnable2,
+ )
+
+ assertThat(runnableSet.addIfAbsent(runnable3)).isTrue()
+ assertThat(runnableSet.toNamedPairs())
+ .containsExactly(
+ "A" to runnable1,
+ "X" to runnable2,
+ "X" to runnable3,
+ )
+
+ assertThat(runnableSet.remove(runnable1)).isTrue()
+ assertThat(runnableSet.toNamedPairs())
+ .containsExactly(
+ "X" to runnable2,
+ "X" to runnable3,
+ )
+
+ assertThat(runnableSet.remove(runnable2)).isTrue()
+ assertThat(runnableSet.toNamedPairs())
+ .containsExactly(
+ "X" to runnable3,
+ )
+ }
+
+ /**
+ * This private method uses [NamedListenerSet.forEachNamed] to produce a list of pairs in order
+ * to validate that method.
+ */
+ private fun <T : Any> NamedListenerSet<T>.toNamedPairs() =
+ sequence { forEachNamed { name, listener -> yield(name to listener) } }.toList()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 62087df..507267e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -97,7 +97,7 @@
fun fakeSceneContainerRepository(
containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
): SceneContainerRepository {
- return SceneContainerRepository(containerConfig)
+ return SceneContainerRepository(applicationScope(), containerConfig)
}
fun fakeSceneKeys(): List<SceneKey> {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
index 1403cea..3fd11a1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
@@ -26,7 +26,7 @@
override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY
override var allDisplays: Array<Display> = displayManager.displays
- private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
+ val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) {
displayCallbacks.add(callback)
@@ -43,12 +43,12 @@
brightnessCallbacks.remove(callback)
}
- fun setDefaultDisplay(displayId: Int) {
- defaultDisplayId = displayId
+ override fun getDisplay(displayId: Int): Display {
+ return allDisplays.filter { display -> display.displayId == displayId }[0]
}
- fun setDisplays(displays: Array<Display>) {
- allDisplays = displays
+ fun setDefaultDisplay(displayId: Int) {
+ defaultDisplayId = displayId
}
fun triggerOnDisplayAdded(displayId: Int) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 81858ee..aadb416 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3089,6 +3089,22 @@
}
}
+ /**
+ * Enforces that the uid of the caller matches the uid of the package.
+ *
+ * @param packageName the name of the package to match uid against.
+ * @param callingUid the uid of the caller.
+ * @throws SecurityException if the calling uid doesn't match uid of the package.
+ */
+ private void enforceCallingPackage(String packageName, int callingUid) {
+ final int userId = UserHandle.getUserId(callingUid);
+ final int packageUid = getPackageManagerInternal().getPackageUid(packageName,
+ /*flags=*/ 0, userId);
+ if (packageUid != callingUid) {
+ throw new SecurityException(packageName + " does not belong to uid " + callingUid);
+ }
+ }
+
@Override
public void setPackageScreenCompatMode(String packageName, int mode) {
mActivityTaskManager.setPackageScreenCompatMode(packageName, mode);
@@ -13637,13 +13653,16 @@
// A backup agent has just come up
@Override
public void backupAgentCreated(String agentPackageName, IBinder agent, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ enforceCallingPackage(agentPackageName, callingUid);
+
// Resolve the target user id and enforce permissions.
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid,
userId, /* allowAll */ false, ALLOW_FULL_ONLY, "backupAgentCreated", null);
if (DEBUG_BACKUP) {
Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent
+ " callingUserId = " + UserHandle.getCallingUserId() + " userId = " + userId
- + " callingUid = " + Binder.getCallingUid() + " uid = " + Process.myUid());
+ + " callingUid = " + callingUid + " uid = " + Process.myUid());
}
synchronized(this) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index c6d6122..80d14a2 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -346,6 +346,9 @@
if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
}
+ Slog.v(TAG, String.format(
+ "Game loading power mode %s (game state change isLoading=%b)",
+ isLoading ? "ON" : "OFF", isLoading));
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
if (isLoading) {
int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
@@ -369,6 +372,7 @@
break;
}
case CANCEL_GAME_LOADING_MODE: {
+ Slog.v(TAG, "Game loading power mode OFF (loading boost ended)");
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
break;
}
@@ -1279,6 +1283,7 @@
// instruction.
mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
} else {
+ Slog.v(TAG, "Game loading power mode ON (loading boost on game start)");
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, true);
}
@@ -1555,6 +1560,10 @@
}
}
}, new IntentFilter(Intent.ACTION_SHUTDOWN));
+ Slog.v(TAG, "Game loading power mode OFF (game manager service start/restart)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
+ Slog.v(TAG, "Game power mode OFF (game manager service start/restart)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
}
private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 683b3eb..247094f 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
import static android.media.AudioSystem.DEVICE_NONE;
import static android.media.AudioSystem.isBluetoothDevice;
@@ -23,8 +24,10 @@
import android.annotation.Nullable;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import java.util.Objects;
@@ -41,8 +44,16 @@
private final int mDeviceType;
private final int mInternalDeviceType;
+
@NonNull
private final String mDeviceAddress;
+
+ /** Unique device id from internal device type and address. */
+ private final Pair<Integer, String> mDeviceId;
+
+ @AudioManager.AudioDeviceCategory
+ private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
+
private boolean mSAEnabled;
private boolean mHasHeadTracker = false;
private boolean mHeadTrackerEnabled;
@@ -68,6 +79,12 @@
}
mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
address) : "";
+
+ mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress);
+ }
+
+ public Pair<Integer, String> getDeviceId() {
+ return mDeviceId;
}
@AudioDeviceInfo.AudioDeviceType
@@ -109,6 +126,15 @@
return mHasHeadTracker;
}
+ @AudioDeviceInfo.AudioDeviceType
+ public int getAudioDeviceCategory() {
+ return mAudioDeviceCategory;
+ }
+
+ public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) {
+ mAudioDeviceCategory = audioDeviceCategory;
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -127,20 +153,23 @@
&& mDeviceAddress.equals(sads.mDeviceAddress) // NonNull
&& mSAEnabled == sads.mSAEnabled
&& mHasHeadTracker == sads.mHasHeadTracker
- && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
+ && mHeadTrackerEnabled == sads.mHeadTrackerEnabled
+ && mAudioDeviceCategory == sads.mAudioDeviceCategory;
}
@Override
public int hashCode() {
return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled,
- mHasHeadTracker, mHeadTrackerEnabled);
+ mHasHeadTracker, mHeadTrackerEnabled, mAudioDeviceCategory);
}
@Override
public String toString() {
return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
- + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled
- + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
+ + " addr: " + mDeviceAddress + " bt audio type: "
+ + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
+ + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker
+ + " HTenabled: " + mHeadTrackerEnabled;
}
public String toPersistableString() {
@@ -150,6 +179,7 @@
.append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
.append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
.append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType)
+ .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory)
.toString());
}
@@ -174,21 +204,27 @@
String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
// we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal
// device type
- if (fields.length != 5 && fields.length != 6) {
- // expecting all fields, fewer may mean corruption, ignore those settings
+ if (fields.length < 5 || fields.length > 7) {
+ // different number of fields may mean corruption, ignore those settings
+ // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory)
return null;
}
try {
final int deviceType = Integer.parseInt(fields[0]);
int internalDeviceType = -1;
- if (fields.length == 6) {
+ if (fields.length >= 6) {
internalDeviceType = Integer.parseInt(fields[5]);
}
+ int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
+ if (fields.length == 7) {
+ audioDeviceCategory = Integer.parseInt(fields[6]);
+ }
final AdiDeviceState deviceState = new AdiDeviceState(deviceType,
internalDeviceType, fields[1]);
deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1);
deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
+ deviceState.setAudioDeviceCategory(audioDeviceCategory);
return deviceState;
} catch (NumberFormatException e) {
Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e);
@@ -200,5 +236,4 @@
return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
mDeviceType, mDeviceAddress);
}
-
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0711ebf..d8266ec 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -63,6 +63,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -2495,13 +2496,13 @@
void onPersistAudioDeviceSettings() {
final String deviceSettings = mDeviceInventory.getDeviceSettings();
- Log.v(TAG, "saving audio device settings: " + deviceSettings);
+ Log.v(TAG, "saving AdiDeviceState: " + deviceSettings);
final SettingsAdapter settings = mAudioService.getSettings();
boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(),
Settings.Secure.AUDIO_DEVICE_INVENTORY,
deviceSettings, UserHandle.USER_CURRENT);
if (!res) {
- Log.e(TAG, "error saving audio device settings: " + deviceSettings);
+ Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
}
}
@@ -2511,7 +2512,7 @@
String settings = settingsAdapter.getSecureStringForUser(contentResolver,
Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
if (settings == null) {
- Log.i(TAG, "reading spatial audio device settings from legacy key"
+ Log.i(TAG, "reading AdiDeviceState from legacy key"
+ Settings.Secure.SPATIAL_AUDIO_ENABLED);
// legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like
// the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid
@@ -2519,21 +2520,21 @@
settings = settingsAdapter.getSecureStringForUser(contentResolver,
Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
if (settings == null) {
- Log.i(TAG, "no spatial audio device settings stored with legacy key");
+ Log.i(TAG, "no AdiDeviceState stored with legacy key");
} else if (!settings.equals("")) {
// Delete old key value and update the new key
if (!settingsAdapter.putSecureStringForUser(contentResolver,
Settings.Secure.SPATIAL_AUDIO_ENABLED,
/*value=*/"",
UserHandle.USER_CURRENT)) {
- Log.w(TAG, "cannot erase the legacy audio device settings with key "
+ Log.w(TAG, "cannot erase the legacy AdiDeviceState with key "
+ Settings.Secure.SPATIAL_AUDIO_ENABLED);
}
if (!settingsAdapter.putSecureStringForUser(contentResolver,
Settings.Secure.AUDIO_DEVICE_INVENTORY,
settings,
UserHandle.USER_CURRENT)) {
- Log.e(TAG, "error updating the new audio device settings with key "
+ Log.e(TAG, "error updating the new AdiDeviceState with key "
+ Settings.Secure.AUDIO_DEVICE_INVENTORY);
}
}
@@ -2553,19 +2554,29 @@
return mDeviceInventory.getDeviceSettings();
}
- List<AdiDeviceState> getImmutableDeviceInventory() {
+ Collection<AdiDeviceState> getImmutableDeviceInventory() {
return mDeviceInventory.getImmutableDeviceInventory();
}
- void addDeviceStateToInventory(AdiDeviceState deviceState) {
- mDeviceInventory.addDeviceStateToInventory(deviceState);
+ void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) {
+ mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState);
}
+ void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
+ mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+ }
+
+ @Nullable
AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
int canonicalType) {
return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType);
}
+ @Nullable
+ AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
+ return mDeviceInventory.findBtDeviceStateForAddress(address, isBle);
+ }
+
//------------------------------------------------
// for testing purposes only
void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index de5ce397..5a92cb4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,6 +15,8 @@
*/
package com.android.server.audio;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET;
import static android.media.AudioSystem.isBluetoothDevice;
import android.annotation.NonNull;
@@ -61,11 +63,13 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -90,38 +94,95 @@
private static final String mMetricsId = "audio.device.";
private final Object mDeviceInventoryLock = new Object();
+
@GuardedBy("mDeviceInventoryLock")
- private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0);
+ private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
-
- List<AdiDeviceState> getImmutableDeviceInventory() {
+ Collection<AdiDeviceState> getImmutableDeviceInventory() {
synchronized (mDeviceInventoryLock) {
- return List.copyOf(mDeviceInventory);
+ return mDeviceInventory.values();
}
}
- void addDeviceStateToInventory(AdiDeviceState deviceState) {
+ /**
+ * Adds a new AdiDeviceState or updates the spatial audio related properties of the matching
+ * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
+ * @param deviceState the device to update
+ */
+ void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) {
synchronized (mDeviceInventoryLock) {
- mDeviceInventory.add(deviceState);
+ mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> {
+ oldState.setHasHeadTracker(newState.hasHeadTracker());
+ oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled());
+ oldState.setSAEnabled(newState.isSAEnabled());
+ return oldState;
+ });
}
}
- AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
- int canonicalDeviceType) {
- final boolean isWireless = isBluetoothDevice(ada.getInternalType());
-
+ /**
+ * Adds a new AdiDeviceState or updates the audio device cateogory of the matching
+ * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
+ * @param deviceState the device to update
+ */
+ void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
synchronized (mDeviceInventoryLock) {
- for (AdiDeviceState deviceSetting : mDeviceInventory) {
- if (deviceSetting.getDeviceType() == canonicalDeviceType
- && (!isWireless || ada.getAddress().equals(
- deviceSetting.getDeviceAddress()))) {
- return deviceSetting;
+ mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> {
+ oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory());
+ return oldState;
+ });
+ }
+ }
+
+ /**
+ * Finds the BT device that matches the passed {@code address}. Currently, this method only
+ * returns a valid device for A2DP and BLE devices.
+ *
+ * @param address MAC address of BT device
+ * @param isBle true if the device is BLE, false for A2DP
+ * @return the found {@link AdiDeviceState} or {@code null} otherwise.
+ */
+ @Nullable
+ AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
+ synchronized (mDeviceInventoryLock) {
+ final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET;
+ for (Integer internalType : deviceSet) {
+ AdiDeviceState deviceState = mDeviceInventory.get(
+ new Pair<>(internalType, address));
+ if (deviceState != null) {
+ return deviceState;
}
}
}
return null;
}
+ /**
+ * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device
+ * type. Note: currently this method only returns a valid device for A2DP and BLE devices.
+ *
+ * @param ada attributes of device to match
+ * @param canonicalDeviceType external device type to match
+ * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or
+ * {@code null} otherwise.
+ */
+ @Nullable
+ AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+ int canonicalDeviceType) {
+ final boolean isWireless = isBluetoothDevice(ada.getInternalType());
+ synchronized (mDeviceInventoryLock) {
+ for (AdiDeviceState deviceState : mDeviceInventory.values()) {
+ if (deviceState.getDeviceType() == canonicalDeviceType
+ && (!isWireless || ada.getAddress().equals(
+ deviceState.getDeviceAddress()))) {
+ return deviceState;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Clears all cached {@link AdiDeviceState}'s. */
void clearDeviceInventory() {
synchronized (mDeviceInventoryLock) {
mDeviceInventory.clear();
@@ -387,7 +448,7 @@
+ " role:" + key.second + " devices:" + devices); });
pw.println("\ndevices:\n");
synchronized (mDeviceInventoryLock) {
- for (AdiDeviceState device : mDeviceInventory) {
+ for (AdiDeviceState device : mDeviceInventory.values()) {
pw.println("\t" + device + "\n");
}
}
@@ -1235,11 +1296,11 @@
AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic(
AudioManager.GET_DEVICES_ALL);
- Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
+ Iterator<Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
rolesMap.entrySet().iterator();
while (itRole.hasNext()) {
- Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
+ Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
itRole.next();
Pair<Integer, Integer> keyRole = entry.getKey();
Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
@@ -2426,19 +2487,20 @@
int deviceCatalogSize = 0;
synchronized (mDeviceInventoryLock) {
deviceCatalogSize = mDeviceInventory.size();
- }
- final StringBuilder settingsBuilder = new StringBuilder(
- deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
- synchronized (mDeviceInventoryLock) {
- for (int i = 0; i < mDeviceInventory.size(); i++) {
- settingsBuilder.append(mDeviceInventory.get(i).toPersistableString());
- if (i != mDeviceInventory.size() - 1) {
- settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
- }
+ final StringBuilder settingsBuilder = new StringBuilder(
+ deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+
+ Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
+ if (iterator.hasNext()) {
+ settingsBuilder.append(iterator.next().toPersistableString());
}
+ while (iterator.hasNext()) {
+ settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
+ settingsBuilder.append(iterator.next().toPersistableString());
+ }
+ return settingsBuilder.toString();
}
- return settingsBuilder.toString();
}
/*package*/ void setDeviceSettings(String settings) {
@@ -2451,7 +2513,8 @@
// Note if the device is not compatible with spatialization mode or the device
// type is not canonical, it will be ignored in {@link SpatializerHelper}.
if (devState != null) {
- addDeviceStateToInventory(devState);
+ addOrUpdateDeviceSAStateInInventory(devState);
+ addOrUpdateAudioDeviceCategoryInInventory(devState);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 19d0a0e..6a73c2b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -19,6 +19,14 @@
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK;
import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
+import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
+import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+import static android.media.AudioManager.DEVICE_OUT_BLE_HEADSET;
+import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER;
+import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_A2DP;
import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
@@ -91,6 +99,7 @@
import android.media.AudioFormat;
import android.media.AudioHalVersionInfo;
import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceCategory;
import android.media.AudioManagerInternal;
import android.media.AudioMixerAttributes;
import android.media.AudioPlaybackConfiguration;
@@ -402,6 +411,7 @@
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
private static final int MSG_INIT_STREAMS_VOLUMES = 101;
private static final int MSG_INIT_SPATIALIZER = 102;
+ private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
// end of messages handled under wakelock
@@ -1250,6 +1260,8 @@
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
+ queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_ADI_DEVICE_STATES,
+ 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
}
@@ -7330,7 +7342,7 @@
if (pkgName == null) {
pkgName = "";
}
- if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
+ if (device.getType() == TYPE_BLUETOOTH_A2DP) {
avrcpSupportsAbsoluteVolume(device.getAddress(),
deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
return;
@@ -9208,6 +9220,11 @@
mAudioEventWakeLock.release();
break;
+ case MSG_INIT_ADI_DEVICE_STATES:
+ onInitAdiDeviceStates();
+ mAudioEventWakeLock.release();
+ break;
+
case MSG_INIT_SPATIALIZER:
onInitSpatializer();
mAudioEventWakeLock.release();
@@ -10272,8 +10289,13 @@
/*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
}
- void onInitSpatializer() {
+ void onInitAdiDeviceStates() {
mDeviceBroker.onReadAudioDeviceSettings();
+ mSoundDoseHelper.initCachedAudioDeviceCategories(
+ mDeviceBroker.getImmutableDeviceInventory());
+ }
+
+ void onInitSpatializer() {
mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
@@ -10667,6 +10689,51 @@
return mSoundDoseHelper.isCsdEnabled();
}
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
+ @AudioDeviceCategory int btAudioDeviceCategory) {
+ super.setBluetoothAudioDeviceCategory_enforcePermission();
+
+ final String addr = Objects.requireNonNull(address);
+
+ AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle);
+
+ int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP
+ : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)
+ ? DEVICE_OUT_BLE_HEADSET : DEVICE_OUT_BLE_SPEAKER);
+ int deviceType = !isBle ? TYPE_BLUETOOTH_A2DP
+ : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) ? TYPE_BLE_HEADSET
+ : TYPE_BLE_SPEAKER);
+
+ if (deviceState == null) {
+ deviceState = new AdiDeviceState(deviceType, internalType, addr);
+ }
+
+ deviceState.setAudioDeviceCategory(btAudioDeviceCategory);
+
+ mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
+ mDeviceBroker.persistAudioDeviceSettings();
+
+ mSoundDoseHelper.setAudioDeviceCategory(addr, internalType,
+ btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @AudioDeviceCategory
+ public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) {
+ super.getBluetoothAudioDeviceCategory_enforcePermission();
+
+ final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(
+ Objects.requireNonNull(address), isBle);
+ if (deviceState == null) {
+ return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+ }
+
+ return deviceState.getAudioDeviceCategory();
+ }
+
//==========================================================================================
// Hdmi CEC:
// - System audio mode:
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 01af3a8..851c5c3 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -16,6 +16,9 @@
package com.android.server.audio;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+
import static com.android.server.audio.AudioService.MAX_STREAM_VOLUME;
import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME;
import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME;
@@ -57,6 +60,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -189,6 +193,9 @@
private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
+ private ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
+ new ArrayList<>();
+
private final Object mCsdStateLock = new Object();
private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>();
@@ -487,6 +494,43 @@
return false;
}
+ void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) {
+ if (!mEnableCsd.get()) {
+ return;
+ }
+
+ final ISoundDose soundDose = mSoundDose.get();
+ if (soundDose == null) {
+ Log.w(TAG, "Sound dose interface not initialized");
+ return;
+ }
+
+ try {
+ final ISoundDose.AudioDeviceCategory audioDeviceCategory =
+ new ISoundDose.AudioDeviceCategory();
+ audioDeviceCategory.address = address;
+ audioDeviceCategory.internalAudioType = internalAudioType;
+ audioDeviceCategory.csdCompatible = isHeadphone;
+ soundDose.setAudioDeviceCategory(audioDeviceCategory);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+ }
+ }
+
+ void initCachedAudioDeviceCategories(Collection<AdiDeviceState> deviceStates) {
+ for (final AdiDeviceState state : deviceStates) {
+ if (state.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_UNKNOWN) {
+ final ISoundDose.AudioDeviceCategory audioDeviceCategory =
+ new ISoundDose.AudioDeviceCategory();
+ audioDeviceCategory.address = state.getDeviceAddress();
+ audioDeviceCategory.internalAudioType = state.getInternalDeviceType();
+ audioDeviceCategory.csdCompatible =
+ state.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES;
+ mCachedAudioDeviceCategories.add(audioDeviceCategory);
+ }
+ }
+ }
+
/*package*/ int safeMediaVolumeIndex(int device) {
final int vol = mSafeMediaVolumeDevices.get(device);
if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) {
@@ -810,6 +854,16 @@
Log.v(TAG, "Initializing sound dose");
+ try {
+ if (mCachedAudioDeviceCategories.size() > 0) {
+ soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray(
+ new ISoundDose.AudioDeviceCategory[0]));
+ mCachedAudioDeviceCategories.clear();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+ }
+
synchronized (mCsdStateLock) {
if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 969dd60..496bdf4 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -560,7 +560,7 @@
updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
ada.getAddress());
initSAState(updatedDevice);
- mDeviceBroker.addDeviceStateToInventory(updatedDevice);
+ mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice);
}
if (updatedDevice != null) {
onRoutingUpdated();
@@ -693,7 +693,7 @@
new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
ada.getAddress());
initSAState(deviceState);
- mDeviceBroker.addDeviceStateToInventory(deviceState);
+ mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState);
mDeviceBroker.persistAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 279aaf9..1898b80 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1308,10 +1308,13 @@
.getString(R.string.biometric_dialog_default_subtitle));
} else if (hasEligibleFingerprintSensor) {
promptInfo.setSubtitle(getContext()
- .getString(R.string.biometric_dialog_fingerprint_subtitle));
+ .getString(R.string.fingerprint_dialog_default_subtitle));
} else if (hasEligibleFaceSensor) {
promptInfo.setSubtitle(getContext()
- .getString(R.string.biometric_dialog_face_subtitle));
+ .getString(R.string.face_dialog_default_subtitle));
+ } else {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.screen_lock_dialog_default_subtitle));
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 9ad4628..2e0274b 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -23,6 +23,7 @@
import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.MotionEvent.TOOL_TYPE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
@@ -44,6 +45,7 @@
import android.util.Printer;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethod;
@@ -351,7 +353,8 @@
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state != null && newState.hasEditorFocused()) {
+ if (state != null && newState.hasEditorFocused()
+ && newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) {
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
newState.setRequestedImeVisible(state.mRequestedImeVisible);
@@ -652,14 +655,23 @@
* A class that represents the current state of the IME target window.
*/
static class ImeTargetWindowState {
+
ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
boolean imeFocusChanged, boolean hasFocusedEditor,
boolean isStartInputByGainFocus) {
+ this(softInputModeState, windowFlags, imeFocusChanged, hasFocusedEditor,
+ isStartInputByGainFocus, TOOL_TYPE_UNKNOWN);
+ }
+
+ ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
+ boolean imeFocusChanged, boolean hasFocusedEditor,
+ boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType) {
mSoftInputModeState = softInputModeState;
mWindowFlags = windowFlags;
mImeFocusChanged = imeFocusChanged;
mHasFocusedEditor = hasFocusedEditor;
mIsStartInputByGainFocus = isStartInputByGainFocus;
+ mToolType = toolType;
}
/**
@@ -670,6 +682,11 @@
private final int mWindowFlags;
/**
+ * {@link MotionEvent#getToolType(int)} that was used to click editor.
+ */
+ private final int mToolType;
+
+ /**
* {@code true} means the IME focus changed from the previous window, {@code false}
* otherwise.
*/
@@ -718,6 +735,10 @@
return mWindowFlags;
}
+ int getToolType() {
+ return mToolType;
+ }
+
private void setImeDisplayId(int imeDisplayId) {
mImeDisplayId = imeDisplayId;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index ba9e280..5308336 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -40,6 +40,7 @@
import android.view.WindowManager;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -295,7 +296,12 @@
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
+ boolean supportsStylusHwChanged =
+ mSupportsStylusHw != info.supportsStylusHandwriting();
mSupportsStylusHw = info.supportsStylusHandwriting();
+ if (supportsStylusHwChanged) {
+ InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
+ }
mService.initializeImeLocked(mCurMethod, mCurToken);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index cfcb462..2a617c5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2369,7 +2369,6 @@
mCurVirtualDisplayToScreenMatrix = null;
ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = null;
- InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
mMenuController.hideInputMethodMenuLocked();
}
}
@@ -3877,11 +3876,14 @@
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
final boolean startInputByWinGainedFocus =
(startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
+ final int toolType = editorInfo != null
+ ? editorInfo.getInitialToolType() : MotionEvent.TOOL_TYPE_UNKNOWN;
// Init the focused window state (e.g. whether the editor has focused or IME focus has
// changed from another window).
- final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode,
- windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus);
+ final ImeTargetWindowState windowState = new ImeTargetWindowState(
+ softInputMode, windowFlags, !sameWindowFocused, isTextEditor,
+ startInputByWinGainedFocus, toolType);
mVisibilityStateComputer.setWindowState(windowToken, windowState);
if (sameWindowFocused && isTextEditor) {
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 446c4f7..8992878 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -25,7 +25,6 @@
import android.content.IntentFilter;
import android.telecom.TelecomManager;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.util.NotificationMessagingUtil;
import java.util.Comparator;
@@ -39,7 +38,6 @@
private final Context mContext;
private final NotificationMessagingUtil mMessagingUtil;
- private final boolean mSortByInterruptiveness;
private String mDefaultPhoneApp;
public NotificationComparator(Context context) {
@@ -47,8 +45,6 @@
mContext.registerReceiver(mPhoneAppBroadcastReceiver,
new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
mMessagingUtil = new NotificationMessagingUtil(mContext);
- mSortByInterruptiveness = !SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS);
}
@Override
@@ -139,14 +135,6 @@
return -1 * Integer.compare(leftPriority, rightPriority);
}
- if (mSortByInterruptiveness) {
- final boolean leftInterruptive = left.isInterruptive();
- final boolean rightInterruptive = right.isInterruptive();
- if (leftInterruptive != rightInterruptive) {
- return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
- }
- }
-
// then break ties by time, most recent first
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ae169318..fbdf750 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -86,7 +86,10 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -127,6 +130,9 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
/**
* Service that manages requests and callbacks for launchers that support
@@ -215,7 +221,8 @@
final LauncherAppsServiceInternal mInternal;
- private RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
+ @NonNull
+ private final RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
public LauncherAppsImpl(Context context) {
mContext = context;
@@ -1462,46 +1469,124 @@
getActivityOptionsForLauncher(opts), user.getIdentifier());
}
+ @Override
+ public void onShellCommand(FileDescriptor in, @NonNull FileDescriptor out,
+ @NonNull FileDescriptor err, @Nullable String[] args, ShellCallback cb,
+ @Nullable ResultReceiver receiver) {
+ final int callingUid = injectBinderCallingUid();
+ if (!(callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID)) {
+ throw new SecurityException("Caller must be shell");
+ }
+
+ final long token = injectClearCallingIdentity();
+ try {
+ int status = (new LauncherAppsShellCommand())
+ .exec(this, in, out, err, args, cb, receiver);
+ if (receiver != null) {
+ receiver.send(status, null);
+ }
+ } finally {
+ injectRestoreCallingIdentity(token);
+ }
+ }
+
+ /** Handles Shell commands for LauncherAppsService */
+ private class LauncherAppsShellCommand extends ShellCommand {
+ @Override
+ public int onCommand(@Nullable String cmd) {
+ if ("dump-view-hierarchies".equals(cmd)) {
+ dumpViewCaptureDataToShell();
+ return 0;
+ } else {
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ private void dumpViewCaptureDataToShell() {
+ try (ZipOutputStream zipOs = new ZipOutputStream(getRawOutputStream())) {
+ forEachViewCaptureWindow((fileName, is) -> {
+ try {
+ zipOs.putNextEntry(new ZipEntry("FS" + fileName));
+ is.transferTo(zipOs);
+ zipOs.closeEntry();
+ } catch (IOException e) {
+ getErrPrintWriter().write("Failed to output " + fileName
+ + " data to shell: " + e.getMessage());
+ }
+ });
+ } catch (IOException e) {
+ getErrPrintWriter().write("Failed to create or close zip output stream: "
+ + e.getMessage());
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Usage: cmd launcherapps COMMAND [options ...]");
+ pw.println();
+ pw.println("cmd launcherapps dump-view-hierarchies");
+ pw.println(" Output captured view hierarchies. Files will be generated in ");
+ pw.println(" `" + WM_TRACE_DIR + "`. After pulling the data to your device,");
+ pw.println(" you can upload / visualize it at `go/winscope`.");
+ pw.println();
+ }
+ }
/**
* Using a pipe, outputs view capture data to the wmtrace dir
*/
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+ @Nullable String[] args) {
super.dump(fd, pw, args);
// Before the wmtrace directory is picked up by dumpstate service, some processes need
// to write their data to that location. They can do that via these dumpCallbacks.
- int i = mDumpCallbacks.beginBroadcast();
- while (i > 0) {
- i--;
- dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i,
- mDumpCallbacks.getBroadcastItem(i));
+ forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+ }
+
+ private void dumpViewCaptureDataToWmTrace(@NonNull String fileName,
+ @NonNull InputStream is) {
+ Path outPath = Paths.get(fileName);
+ try {
+ Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING);
+ Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS);
+ } catch (IOException e) {
+ Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e);
+ }
+ }
+
+ /**
+ * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data
+ * to LauncherAppsService via the pipe's input provided. This data (as well as an output
+ * file name) is provided to the consumer via an InputStream to output where it wants (for
+ * example, the winscope trace directory or the shell's stdout).
+ */
+ private void forEachViewCaptureWindow(
+ @NonNull BiConsumer<String, InputStream> outputtingConsumer) {
+ for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
+ String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
+ String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
+
+ try {
+ // Order is important here. OnDump needs to be called before the BiConsumer
+ // accepts & starts blocking on reading the input stream.
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
+
+ InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
+ outputtingConsumer.accept(fileName, is);
+ is.close();
+ } catch (Exception e) {
+ Log.d(TAG, "failed to pipe view capture data", e);
+ }
}
mDumpCallbacks.finishBroadcast();
}
- private void dumpDataToWmTrace(String name, IDumpCallback cb) {
- ParcelFileDescriptor[] pipe;
- try {
- pipe = ParcelFileDescriptor.createPipe();
- cb.onDump(pipe[1]);
- } catch (IOException | RemoteException e) {
- Log.d(TAG, "failed to pipe view capture data", e);
- return;
- }
-
- Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName());
- try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) {
- Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
- Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS);
- } catch (IOException e) {
- Log.d(TAG, "failed to write data to file in wmtrace dir", e);
- }
- }
-
@RequiresPermission(READ_FRAME_BUFFER)
@Override
- public void registerDumpCallback(IDumpCallback cb) {
+ public void registerDumpCallback(@NonNull IDumpCallback cb) {
int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
if (PERMISSION_GRANTED == status) {
String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
@@ -1513,7 +1598,7 @@
@RequiresPermission(READ_FRAME_BUFFER)
@Override
- public void unRegisterDumpCallback(IDumpCallback cb) {
+ public void unRegisterDumpCallback(@NonNull IDumpCallback cb) {
int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
if (PERMISSION_GRANTED == status) {
mDumpCallbacks.unregister(cb);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index d1ae7de..0388496 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -309,6 +309,14 @@
applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
deferred);
if (needsSetReady) {
+ // TODO(b/294925498): Remove this once we have accurate ready
+ // tracking.
+ if (hasActivityLaunch(wct) && !mService.mRootWindowContainer
+ .allPausedActivitiesComplete()) {
+ // WCT is launching an activity, so we need to wait for its
+ // lifecycle events.
+ return;
+ }
nextTransition.setAllReady();
}
});
@@ -344,6 +352,15 @@
}
}
+ private static boolean hasActivityLaunch(WindowContainerTransaction wct) {
+ for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+ if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
@NonNull IWindowContainerTransactionCallback callback,
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index cd3a78e..6906dec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2157,6 +2157,14 @@
}
@Test
+ public void testResetGamePowerMode() {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.onBootCompleted();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ }
+
+ @Test
public void testNotifyGraphicsEnvironmentSetup() {
String configString = "mode=2,loadingBoost=2000";
when(DeviceConfig.getProperty(anyString(), anyString()))
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index fc62e75..e79ac09 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -116,9 +117,9 @@
private static final String ERROR_LOCKOUT = "error_lockout";
private static final String FACE_SUBTITLE = "face_subtitle";
private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+ private static final String CREDENTIAL_SUBTITLE = "credential_subtitle";
private static final String DEFAULT_SUBTITLE = "default_subtitle";
-
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
private static final int SENSOR_ID_FINGERPRINT = 0;
@@ -143,6 +144,8 @@
@Mock
IBiometricAuthenticator mFaceAuthenticator;
@Mock
+ IBiometricAuthenticator mCredentialAuthenticator;
+ @Mock
ITrustManager mTrustManager;
@Mock
DevicePolicyManager mDevicePolicyManager;
@@ -196,10 +199,12 @@
.thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
- when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+ when(mContext.getString(R.string.face_dialog_default_subtitle))
.thenReturn(FACE_SUBTITLE);
- when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+ when(mContext.getString(R.string.fingerprint_dialog_default_subtitle))
.thenReturn(FINGERPRINT_SUBTITLE);
+ when(mContext.getString(R.string.screen_lock_dialog_default_subtitle))
+ .thenReturn(CREDENTIAL_SUBTITLE);
when(mContext.getString(R.string.biometric_dialog_default_subtitle))
.thenReturn(DEFAULT_SUBTITLE);
@@ -292,7 +297,8 @@
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- Authenticators.DEVICE_CREDENTIAL);
+ Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_CREDENTIAL),
@@ -312,7 +318,8 @@
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- Authenticators.DEVICE_CREDENTIAL);
+ Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
assertNotNull(mBiometricService.mAuthSession);
@@ -338,7 +345,8 @@
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -357,7 +365,8 @@
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(TYPE_FINGERPRINT),
@@ -370,7 +379,8 @@
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- Authenticators.BIOMETRIC_STRONG);
+ Authenticators.BIOMETRIC_STRONG, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -429,7 +439,8 @@
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(TYPE_FINGERPRINT),
@@ -441,9 +452,9 @@
public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */,
- null);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, true /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
@@ -453,9 +464,9 @@
public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */,
- null);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, true /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
assertEquals(FINGERPRINT_SUBTITLE,
@@ -463,6 +474,19 @@
}
@Test
+ public void testAuthenticateFingerprint_shouldShowSubtitleForCredential() throws Exception {
+ setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, true /* useDefaultSubtitle */,
+ true /* deviceCredentialAllowed */);
+ waitForIdle();
+
+ assertEquals(CREDENTIAL_SUBTITLE,
+ mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
@@ -476,9 +500,9 @@
setupAuthForMultiple(modalities, strengths);
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */,
- null);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, true /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
@@ -492,7 +516,8 @@
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -506,7 +531,8 @@
anyInt() /* modality */, anyInt() /* userId */))
.thenReturn(true);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
final byte[] HAT = generateRandomHAT();
@@ -524,7 +550,8 @@
anyInt() /* modality */, anyInt() /* userId */))
.thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
SENSOR_ID_FACE,
@@ -552,7 +579,8 @@
throws Exception {
// Start testing the happy path
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
// Creates a pending auth session with the correct initial states
@@ -632,7 +660,8 @@
.thenReturn(true);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */,
- Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
+ false /* useDefaultSubtitle*/, false /* deviceCredentialAllowed */);
waitForIdle();
assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
@@ -702,7 +731,8 @@
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */,
- Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG);
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG,
+ false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(anyInt() /* modality */,
@@ -754,7 +784,8 @@
false /* requireConfirmation */, null /* authenticators */);
invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
- null /* authenticators */);
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
@@ -887,7 +918,8 @@
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
- Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
+ false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
waitForIdle();
assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
@@ -920,8 +952,9 @@
public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
throws Exception {
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, null /* authenticators */);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
mBiometricService.mAuthSession.mSensorReceiver.onError(
@@ -957,8 +990,9 @@
setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(lockoutMode);
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, null /* authenticators */);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
// Modality and error are sent
@@ -996,8 +1030,9 @@
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(lockoutMode);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, null /* authenticators */);
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ null /* authenticators */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
// The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
@@ -1014,7 +1049,8 @@
.thenReturn(LockoutTracker.LOCKOUT_PERMANENT);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
- Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG);
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG,
+ false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
@@ -1503,7 +1539,8 @@
assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
invokeCanAuthenticate(mBiometricService, authenticators));
long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, authenticators);
+ false /* requireConfirmation */, authenticators, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
verify(mReceiver1).onError(
eq(TYPE_FINGERPRINT),
@@ -1539,7 +1576,8 @@
invokeCanAuthenticate(mBiometricService, authenticators));
requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
- authenticators);
+ authenticators, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */);
waitForIdle();
assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1749,6 +1787,11 @@
mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength,
mFaceAuthenticator);
}
+
+ if ((modality & TYPE_CREDENTIAL) != 0) {
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+ .thenReturn(true);
+ }
}
// TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
@@ -1799,7 +1842,8 @@
Integer authenticators) throws Exception {
// Request auth, creates a pending session
final long requestId = invokeAuthenticate(
- service, receiver, requireConfirmation, authenticators);
+ service, receiver, requireConfirmation, authenticators,
+ false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
waitForIdle();
startPendingAuthSession(mBiometricService);
@@ -1827,7 +1871,8 @@
private static long invokeAuthenticate(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
- Integer authenticators) throws Exception {
+ Integer authenticators, boolean useDefaultSubtitle,
+ boolean deviceCredentialAllowed) throws Exception {
return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
@@ -1835,7 +1880,8 @@
receiver,
TEST_PACKAGE_NAME /* packageName */,
createTestPromptInfo(requireConfirmation, authenticators,
- false /* checkDevicePolicy */));
+ false /* checkDevicePolicy */, useDefaultSubtitle,
+ deviceCredentialAllowed));
}
private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
@@ -1847,16 +1893,19 @@
receiver,
TEST_PACKAGE_NAME /* packageName */,
createTestPromptInfo(false /* requireConfirmation */, authenticators,
- true /* checkDevicePolicy */));
+ true /* checkDevicePolicy */, false /* useDefaultSubtitle */,
+ false /* deviceCredentialAllowed */));
}
private static PromptInfo createTestPromptInfo(
boolean requireConfirmation,
Integer authenticators,
- boolean checkDevicePolicy) {
+ boolean checkDevicePolicy,
+ boolean useDefaultSubtitle,
+ boolean deviceCredentialAllowed) {
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setConfirmationRequested(requireConfirmation);
- promptInfo.setUseDefaultSubtitle(true);
+ promptInfo.setUseDefaultSubtitle(useDefaultSubtitle);
if (authenticators != null) {
promptInfo.setAuthenticators(authenticators);
@@ -1864,6 +1913,7 @@
if (checkDevicePolicy) {
promptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicy);
}
+ promptInfo.setDeviceCredentialAllowed(deviceCredentialAllowed);
return promptInfo;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 95fae07..22b1127 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -15,8 +15,6 @@
*/
package com.android.server.notification;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -47,6 +45,8 @@
import android.telecom.TelecomManager;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.server.UiServiceTestCase;
@@ -54,7 +54,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -63,7 +62,7 @@
import java.util.List;
@SmallTest
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
public class NotificationComparatorTest extends UiServiceTestCase {
@Mock Context mMockContext;
@Mock TelecomManager mTm;
@@ -97,24 +96,9 @@
private NotificationRecord mRecordColorized;
private NotificationRecord mRecordColorizedCall;
- @Parameterized.Parameters(name = "sortByInterruptiveness={0}")
- public static Boolean[] getSortByInterruptiveness() {
- return new Boolean[] { true, false };
- }
-
- @Parameterized.Parameter
- public boolean mSortByInterruptiveness;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
- if (flag.mSysPropKey.equals(NO_SORT_BY_INTERRUPTIVENESS.mSysPropKey)) {
- return !mSortByInterruptiveness;
- }
- return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
- };
-
int userId = UserHandle.myUserId();
final Resources res = mContext.getResources();
@@ -309,13 +293,8 @@
expected.add(mNoMediaSessionMedia);
expected.add(mRecordCheater);
expected.add(mRecordCheaterColorized);
- if (mSortByInterruptiveness) {
- expected.add(mRecordMinCall);
- expected.add(mRecordMinCallNonInterruptive);
- } else {
- expected.add(mRecordMinCallNonInterruptive);
- expected.add(mRecordMinCall);
- }
+ expected.add(mRecordMinCallNonInterruptive);
+ expected.add(mRecordMinCall);
List<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
@@ -330,11 +309,7 @@
public void testRankingScoreOverrides() {
NotificationComparator comp = new NotificationComparator(mMockContext);
NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
- if (mSortByInterruptiveness) {
- assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
- } else {
- assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
- }
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index 60172a3..d35dbb56 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,3 +1,7 @@
+aprasath@google.com
+kumarashishg@google.com
+sarup@google.com
+anothermark@google.com
badhri@google.com
elaurent@google.com
albertccwang@google.com