diff options
56 files changed, 1517 insertions, 288 deletions
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 7be00a045403..3487e0b1f3e8 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -827,6 +827,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4; + /** + * Whether AbiOverride was used when installing this application. + * @hide + */ + public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = { PRIVATE_FLAG_EXT_PROFILEABLE, @@ -834,6 +840,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE, PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK, PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS, + PRIVATE_FLAG_EXT_CPU_OVERRIDE, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlagsExt {} diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 931adb55a686..ef274a56e1d3 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -23,6 +23,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.Activity; +import android.app.ActivityOptions; import android.app.Application.ActivityLifecycleCallbacks; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -535,7 +536,11 @@ public final class PrintManager { return null; } try { - mContext.startIntentSender(intent, null, 0, 0, 0); + ActivityOptions activityOptions = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + mContext.startIntentSender(intent, null, 0, 0, 0, + activityOptions.toBundle()); return new PrintJob(printJob, this); } catch (SendIntentException sie) { Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 7e4e4022f00f..5019b85ca503 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1099,21 +1099,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // TODO: Support a ResultReceiver for IME. // TODO(b/123718661): Make show() work for multi-session IME. int typesReady = 0; + final boolean imeVisible = mState.isSourceOrDefaultVisible( + mImeSourceConsumer.getId(), ime()); for (int type = FIRST; type <= LAST; type = type << 1) { if ((types & type) == 0) { continue; } @AnimationType final int animationType = getAnimationType(type); final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0; - final boolean isImeAnimation = type == ime(); - if (requestedVisible && animationType == ANIMATION_TYPE_NONE - || animationType == ANIMATION_TYPE_SHOW) { + final boolean isIme = type == ime(); + var alreadyVisible = requestedVisible && (!isIme || imeVisible) + && animationType == ANIMATION_TYPE_NONE; + var alreadyAnimatingShow = animationType == ANIMATION_TYPE_SHOW; + if (alreadyVisible || alreadyAnimatingShow) { // no-op: already shown or animating in (because window visibility is // applied before starting animation). if (DEBUG) Log.d(TAG, String.format( "show ignored for type: %d animType: %d requestedVisible: %s", type, animationType, requestedVisible)); - if (isImeAnimation) { + if (isIme) { ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); } @@ -1121,13 +1125,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } if (fromIme && animationType == ANIMATION_TYPE_USER) { // App is already controlling the IME, don't cancel it. - if (isImeAnimation) { + if (isIme) { ImeTracker.forLogging().onFailed( statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); } continue; } - if (isImeAnimation) { + if (isIme) { ImeTracker.forLogging().onProgress( statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); } diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 3d95dd341cc0..c9e76009136a 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags { /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); + + /** Gating the holding of WakeLocks until NLSes are told about a new notification. */ + public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION = + devFlag("persist.sysui.notification.wake_lock_for_posting_notification"); } //// == End of flags. Everything below this line is the implementation. == //// diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt index 89871fa7d875..2cd587ffbc45 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt @@ -56,7 +56,19 @@ data class TurbulenceNoiseAnimationConfig( val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS, val pixelDensity: Float = 1f, val blendMode: BlendMode = DEFAULT_BLEND_MODE, - val onAnimationEnd: Runnable? = null + val onAnimationEnd: Runnable? = null, + /** + * Variants in noise. Higher number means more contrast; lower number means less contrast but + * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate. + * Expected range [0, 1]. + */ + val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR, + /** + * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may + * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected + * range [0, 1]. + */ + val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS ) { companion object { const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec @@ -66,6 +78,8 @@ data class TurbulenceNoiseAnimationConfig( const val DEFAULT_NOISE_SPEED_Z = 0.3f const val DEFAULT_OPACITY = 150 // full opacity is 255. const val DEFAULT_COLOR = Color.WHITE + const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f + const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f const val DEFAULT_BACKGROUND_COLOR = Color.BLACK val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt index d1ba7c4de35c..d3c57c91405a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt @@ -37,6 +37,8 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : uniform float in_opacity; uniform float in_pixelDensity; uniform float in_inverseLuma; + uniform half in_lumaMatteBlendFactor; + uniform half in_lumaMatteOverallBrightness; layout(color) uniform vec4 in_color; layout(color) uniform vec4 in_backgroundColor; """ @@ -48,18 +50,21 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity; + // Bring it to [0, 1] range. + float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5; + luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) + * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; - // Add dither with triangle distribution to avoid color banding. Ok to dither in the + // Add dither with triangle distribution to avoid color banding. Dither in the // shader here as we are in gamma space. float dither = triangleNoise(p * in_pixelDensity) / 255.; // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to // multiply rgb with a to get the correct result. - color = (color + dither.rrr) * in_color.a; - return vec4(color, in_color.a); + color = (color + dither.rrr) * in_opacity; + return vec4(color, in_opacity); } """ @@ -70,12 +75,15 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity; + // Bring it to [0, 1] range. + float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5; + luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) + * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; // Skip dithering. - return vec4(color * in_color.a, in_color.a); + return vec4(color * in_opacity, in_opacity); } """ @@ -125,6 +133,28 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : } /** + * Sets blend and brightness factors of the luma matte. + * + * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting + * this a lower number removes variations. I.e. the turbulence noise will look more blended. + * Expected input range is [0, 1]. more dimmed. + * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise. + * Expected input range is [0, 1]. + * + * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2, + * which makes the noise look softer. However it makes the overall noise look dim, so you want + * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall + * brightness. + */ + fun setLumaMatteFactors( + lumaMatteBlendFactor: Float = 1f, + lumaMatteOverallBrightness: Float = 0f + ) { + setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor) + setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness) + } + + /** * Sets whether to inverse the luminosity of the noise. * * By default noise will be used as a luma matte as is. This means that you will see color in @@ -132,7 +162,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : * true. */ fun setInverseNoiseLuminosity(inverse: Boolean) { - setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f) + setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f) } /** Current noise movements in x, y, and z axes. */ diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt index c3e84787d4fb..43d6504fce84 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt @@ -215,10 +215,12 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex noiseConfig = config with(turbulenceNoiseShader) { setGridCount(config.gridCount) - setColor(ColorUtils.setAlphaComponent(config.color, config.opacity)) + setColor(config.color) setBackgroundColor(config.backgroundColor) setSize(config.width, config.height) setPixelDensity(config.pixelDensity) + setInverseNoiseLuminosity(inverse = false) + setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness) } paint.blendMode = config.blendMode } diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml index a8f0cc3a1d92..4a9d41fae1d5 100644 --- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml +++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml @@ -14,20 +14,6 @@ Copyright (C) 2015 The Android Open Source Project limitations under the License. --> <inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetLeft="3dp" - android:insetRight="3dp"> - <vector android:width="18dp" - android:height="18dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M16,16L2.81,2.81L1.39,4.22l4.85,4.85C6.09,9.68 6,10.33 6,11v6H4v2h12.17l3.61,3.61l1.41,-1.41L16,16zM8,17c0,0 0.01,-6.11 0.01,-6.16L14.17,17H8z"/> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,6.5c2.49,0 4,2.02 4,4.5v2.17l2,2V11c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.72,4.86 9.05,5.2 8.46,5.63L9.93,7.1C10.51,6.73 11.2,6.5 12,6.5z"/> - </vector> -</inset> + android:insetLeft="3dp" + android:insetRight="3dp" + android:drawable="@drawable/ic_speaker_mute" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8d3ba364da06..4f768cc39b40 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -572,6 +572,7 @@ <dimen name="qs_brightness_margin_bottom">16dp</dimen> <dimen name="qqs_layout_margin_top">16dp</dimen> <dimen name="qqs_layout_padding_bottom">24dp</dimen> + <item name="qqs_expand_clock_scale" format="float" type="dimen">2.57</item> <!-- Most of the time it should be the same as notification_side_paddings as it's vertically aligned with notifications. The exception is split shade when this value becomes diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b48296fe54be..58be3e9d4e2f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2724,8 +2724,8 @@ <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string> <!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] --> <string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string> - <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] --> - <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string> + <!-- The hint message when Broadcast edit is more than 16/254 characters [CHAR LIMIT=60] --> + <string name="media_output_broadcast_edit_hint_no_more_than_max">Use fewer than <xliff:g id="length" example="16">%1$d</xliff:g> characters</string> <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_clip_data_label">Build number</string> diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml index 52a98984e6e2..8039c68485ca 100644 --- a/packages/SystemUI/res/xml/qs_header.xml +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -43,8 +43,8 @@ app:layout_constraintBottom_toBottomOf="@id/carrier_group" /> <Transform - android:scaleX="2.57" - android:scaleY="2.57" + android:scaleX="@dimen/qqs_expand_clock_scale" + android:scaleY="@dimen/qqs_expand_clock_scale" /> </Constraint> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index f86e2ed6eb91..7f706859abb3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -614,7 +614,11 @@ public class AuthContainerView extends LinearLayout return ((AuthBiometricFingerprintView) view).isUdfps(); } if (view instanceof BiometricPromptLayout) { - return ((BiometricPromptLayout) view).isUdfps(); + // this will force the prompt to align itself on the edge of the screen + // instead of centering (temporary workaround to prevent small implicit view + // from breaking due to the way gravity / margins are set in the legacy + // AuthPanelController + return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index baaa96efb5f0..d48b9c339d15 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -35,6 +35,7 @@ import android.os.Handler import android.util.Log import android.util.RotationUtils import android.view.Display +import android.view.DisplayInfo import android.view.Gravity import android.view.LayoutInflater import android.view.Surface @@ -58,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.util.boundsOnScreen import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.traceSection import java.io.PrintWriter @@ -129,6 +131,8 @@ constructor( } @VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT + private val displayInfo = DisplayInfo() + private val overlayViewParams = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, @@ -214,6 +218,23 @@ constructor( for (requestSource in requests) { pw.println(" $requestSource.name") } + + pw.println("overlayView:") + pw.println(" width=${overlayView?.width}") + pw.println(" height=${overlayView?.height}") + pw.println(" boundsOnScreen=${overlayView?.boundsOnScreen}") + + pw.println("displayStateInteractor:") + pw.println(" isInRearDisplayMode=${displayStateInteractor?.isInRearDisplayMode?.value}") + + pw.println("sensorProps:") + pw.println(" displayId=${displayInfo.uniqueId}") + pw.println(" sensorType=${sensorProps?.sensorType}") + pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}") + + pw.println("overlayOffsets=$overlayOffsets") + pw.println("isReverseDefaultRotation=$isReverseDefaultRotation") + pw.println("currentRotation=${displayInfo.rotation}") } private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) { @@ -226,6 +247,8 @@ constructor( val view = layoutInflater.inflate(R.layout.sidefps_view, null, false) overlayView = view val display = context.display!! + // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation + display.getDisplayInfo(displayInfo) val offsets = sensorProps.getLocation(display.uniqueId).let { location -> if (location == null) { @@ -239,12 +262,12 @@ constructor( view.rotation = display.asSideFpsAnimationRotation( offsets.isYAligned(), - getRotationFromDefault(display.rotation) + getRotationFromDefault(displayInfo.rotation) ) lottie.setAnimation( display.asSideFpsAnimation( offsets.isYAligned(), - getRotationFromDefault(display.rotation) + getRotationFromDefault(displayInfo.rotation) ) ) lottie.addLottieOnCompositionLoadedListener { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index e4c4e9aedb56..1dffa80a084f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -19,8 +19,11 @@ package com.android.systemui.biometrics.ui.binder import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator +import android.view.Surface import android.view.View import android.view.ViewGroup +import android.view.WindowInsets +import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.widget.TextView import androidx.core.animation.addListener @@ -52,7 +55,9 @@ object BiometricViewSizeBinder { panelViewController: AuthPanelController, jankListener: BiometricJankListener, ) { - val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! + val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java)) + val accessibilityManager = + requireNotNull(view.context.getSystemService(AccessibilityManager::class.java)) fun notifyAccessibilityChanged() { Utils.notifyAccessibilityContentChanged(accessibilityManager, view) } @@ -102,15 +107,26 @@ object BiometricViewSizeBinder { when { size.isSmall -> { iconHolderView.alpha = 1f + val bottomInset = + windowManager.maximumWindowMetrics.windowInsets + .getInsets(WindowInsets.Type.navigationBars()) + .bottom iconHolderView.y = - view.height - iconHolderView.height - iconPadding + if (view.isLandscape()) { + (view.height - iconHolderView.height - bottomInset) / 2f + } else { + view.height - + iconHolderView.height - + iconPadding - + bottomInset + } val newHeight = - iconHolderView.height + 2 * iconPadding.toInt() - + iconHolderView.height + (2 * iconPadding.toInt()) - iconHolderView.paddingTop - iconHolderView.paddingBottom panelViewController.updateForContentDimensions( width, - newHeight, + newHeight + bottomInset, 0, /* animateDurationMs */ ) } @@ -181,6 +197,11 @@ object BiometricViewSizeBinder { } } +private fun View.isLandscape(): Boolean { + val r = context.display.rotation + return r == Surface.ROTATION_90 || r == Surface.ROTATION_270 +} + private fun TextView.showTextOrHide(forceHide: Boolean = false) { visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 44e74e7e339b..9db3c22dff84 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -63,7 +63,7 @@ constructor( val hasCards = response?.walletCards?.isNotEmpty() == true trySendWithFailureLogging( state( - isFeatureEnabled = walletController.isWalletEnabled, + isFeatureEnabled = isWalletAvailable(), hasCard = hasCards, tileIcon = walletController.walletClient.tileIcon, ), @@ -100,7 +100,7 @@ constructor( return when { !walletController.walletClient.isWalletServiceAvailable -> KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice - !walletController.isWalletEnabled || queryCards().isEmpty() -> { + !isWalletAvailable() || queryCards().isEmpty() -> { KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( instructions = listOf( @@ -146,6 +146,11 @@ constructor( } } + private fun isWalletAvailable() = + with(walletController.walletClient) { + isWalletServiceAvailable && isWalletFeatureAvailable + } + private fun state( isFeatureEnabled: Boolean, hasCard: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 516fbf5ca12c..14386c1c0fd6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -76,7 +76,6 @@ import androidx.constraintlayout.widget.ConstraintSet; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.ColorUtils; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; import com.android.internal.widget.CachingIconView; @@ -1211,24 +1210,24 @@ public class MediaControlPanel { private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() { return new TurbulenceNoiseAnimationConfig( - TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT, + /* gridCount= */ 2.14f, TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER, - /* noiseMoveSpeedX= */ 0f, + /* noiseMoveSpeedX= */ 0.42f, /* noiseMoveSpeedY= */ 0f, TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z, /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(), - // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art. - // Thus, set the background color with alpha 0. - /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0), - TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY, + /* backgroundColor= */ Color.BLACK, + /* opacity= */ 51, /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(), /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(), TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS, - /* easeInDuration= */ 2500f, - /* easeOutDuration= */ 2500f, + /* easeInDuration= */ 1350f, + /* easeOutDuration= */ 1350f, this.getContext().getResources().getDisplayMetrics().density, - BlendMode.PLUS, - /* onAnimationEnd= */ null + BlendMode.SCREEN, + /* onAnimationEnd= */ null, + /* lumaMatteBlendFactor= */ 0.26f, + /* lumaMatteOverallBrightness= */ 0.09f ); } private void clearButton(final ImageButton button) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 529b980a6e38..b4578e97eda2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -59,6 +59,17 @@ import com.google.zxing.WriterException; public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { private static final String TAG = "MediaOutputBroadcastDialog"; + static final int METADATA_BROADCAST_NAME = 0; + static final int METADATA_BROADCAST_CODE = 1; + + private static final int MAX_BROADCAST_INFO_UPDATE = 3; + @VisibleForTesting + static final int BROADCAST_CODE_MAX_LENGTH = 16; + @VisibleForTesting + static final int BROADCAST_CODE_MIN_LENGTH = 4; + @VisibleForTesting + static final int BROADCAST_NAME_MAX_LENGTH = 254; + private ViewStub mBroadcastInfoArea; private ImageView mBroadcastQrCodeView; private ImageView mBroadcastNotify; @@ -68,14 +79,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { private ImageView mBroadcastCodeEye; private Boolean mIsPasswordHide = true; private ImageView mBroadcastCodeEdit; - private AlertDialog mAlertDialog; + @VisibleForTesting + AlertDialog mAlertDialog; private TextView mBroadcastErrorMessage; private int mRetryCount = 0; private String mCurrentBroadcastName; private String mCurrentBroadcastCode; private boolean mIsStopbyUpdateBroadcastCode = false; + private boolean mIsLeBroadcastAssistantCallbackRegistered; - private TextWatcher mTextWatcher = new TextWatcher() { + private TextWatcher mBroadcastCodeTextWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // Do nothing @@ -103,7 +116,9 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { R.string.media_output_broadcast_code_hint_no_less_than_min); } else if (breakBroadcastCodeRuleTextLengthMoreThanMax) { mBroadcastErrorMessage.setText( - R.string.media_output_broadcast_code_hint_no_more_than_max); + mContext.getResources().getString( + R.string.media_output_broadcast_edit_hint_no_more_than_max, + BROADCAST_CODE_MAX_LENGTH)); } mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE); @@ -114,7 +129,40 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } }; - private boolean mIsLeBroadcastAssistantCallbackRegistered; + private TextWatcher mBroadcastNameTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing + } + + @Override + public void afterTextChanged(Editable s) { + if (mAlertDialog == null || mBroadcastErrorMessage == null) { + return; + } + boolean breakBroadcastNameRuleTextLengthMoreThanMax = + s.length() > BROADCAST_NAME_MAX_LENGTH; + boolean breakRule = breakBroadcastNameRuleTextLengthMoreThanMax || (s.length() == 0); + + if (breakBroadcastNameRuleTextLengthMoreThanMax) { + mBroadcastErrorMessage.setText( + mContext.getResources().getString( + R.string.media_output_broadcast_edit_hint_no_more_than_max, + BROADCAST_NAME_MAX_LENGTH)); + } + mBroadcastErrorMessage.setVisibility( + breakBroadcastNameRuleTextLengthMoreThanMax ? View.VISIBLE : View.INVISIBLE); + Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + if (positiveBtn != null) { + positiveBtn.setEnabled(breakRule ? false : true); + } + } + }; private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = new BluetoothLeBroadcastAssistant.Callback() { @@ -187,13 +235,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } }; - static final int METADATA_BROADCAST_NAME = 0; - static final int METADATA_BROADCAST_CODE = 1; - - private static final int MAX_BROADCAST_INFO_UPDATE = 3; - private static final int BROADCAST_CODE_MAX_LENGTH = 16; - private static final int BROADCAST_CODE_MIN_LENGTH = 4; - MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { super(context, broadcastSender, mediaOutputController); @@ -392,13 +433,12 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { R.layout.media_output_broadcast_update_dialog, null); final EditText editText = layout.requireViewById(R.id.broadcast_edit_text); editText.setText(editString); - if (isBroadcastCode) { - editText.addTextChangedListener(mTextWatcher); - } + editText.addTextChangedListener( + isBroadcastCode ? mBroadcastCodeTextWatcher : mBroadcastNameTextWatcher); mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message); mAlertDialog = new Builder(mContext) .setTitle(isBroadcastCode ? R.string.media_output_broadcast_code - : R.string.media_output_broadcast_name) + : R.string.media_output_broadcast_name) .setView(layout) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.media_output_broadcast_dialog_save, diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 897b0e73dca0..5d028307a62d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -65,14 +65,12 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.DisplayTracker; -import dagger.Lazy; - import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; - +import dagger.Lazy; public class CustomTile extends QSTileImpl<State> implements TileChangeListener { public static final String PREFIX = "custom("; diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index a066242fd96b..d7ae575724dd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository +import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor @@ -45,6 +47,11 @@ abstract class QSPipelineModule { ): CurrentTilesInteractor @Binds + abstract fun provideInstalledTilesPackageRepository( + impl: InstalledTilesComponentRepositoryImpl + ): InstalledTilesComponentRepository + + @Binds @IntoMap @ClassKey(PrototypeCoreStartable::class) abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt new file mode 100644 index 000000000000..498f403e8c7a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt @@ -0,0 +1,109 @@ +/* + * 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.qs.pipeline.data.repository + +import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE +import android.annotation.WorkerThread +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.content.pm.PackageManager.ResolveInfoFlags +import android.os.UserHandle +import android.service.quicksettings.TileService +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.kotlin.isComponentActuallyEnabled +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +interface InstalledTilesComponentRepository { + + fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> +} + +@SysUISingleton +class InstalledTilesComponentRepositoryImpl +@Inject +constructor( + @Application private val applicationContext: Context, + private val packageManager: PackageManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : InstalledTilesComponentRepository { + + override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> = + conflatedCallbackFlow { + val receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + trySend(Unit) + } + } + applicationContext.registerReceiverAsUser( + receiver, + UserHandle.of(userId), + INTENT_FILTER, + /* broadcastPermission = */ null, + /* scheduler = */ null + ) + + awaitClose { applicationContext.unregisterReceiver(receiver) } + } + .onStart { emit(Unit) } + .map { reloadComponents(userId) } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + + @WorkerThread + private fun reloadComponents(userId: Int): Set<ComponentName> { + return packageManager + .queryIntentServicesAsUser(INTENT, FLAGS, userId) + .mapNotNull { it.serviceInfo } + .filter { it.permission == BIND_QUICK_SETTINGS_TILE } + .filter { packageManager.isComponentActuallyEnabled(it) } + .mapTo(mutableSetOf()) { it.componentName } + } + + companion object { + private val INTENT_FILTER = + IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_CHANGED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addDataScheme("package") + } + private val INTENT = Intent(TileService.ACTION_QS_TILE) + private val FLAGS = + ResolveInfoFlags.of( + (PackageManager.GET_SERVICES or + PackageManager.MATCH_DIRECT_BOOT_AWARE or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE) + .toLong() + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt index 3b2362f2b326..a162d113a3b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt @@ -42,6 +42,8 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext /** Repository that tracks the current tiles. */ @@ -104,6 +106,8 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) : TileSpecRepository { + private val mutex = Mutex() + private val retailModeTiles by lazy { resources .getString(R.string.quick_settings_tiles_retail_mode) @@ -145,37 +149,40 @@ constructor( .flowOn(backgroundDispatcher) } - override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) { - if (tile == TileSpec.Invalid) { - return - } - val tilesList = loadTiles(userId).toMutableList() - if (tile !in tilesList) { - if (position < 0 || position >= tilesList.size) { - tilesList.add(tile) - } else { - tilesList.add(position, tile) + override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) = + mutex.withLock { + if (tile == TileSpec.Invalid) { + return + } + val tilesList = loadTiles(userId).toMutableList() + if (tile !in tilesList) { + if (position < 0 || position >= tilesList.size) { + tilesList.add(tile) + } else { + tilesList.add(position, tile) + } + storeTiles(userId, tilesList) } - storeTiles(userId, tilesList) } - } - override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) { - if (tiles.all { it == TileSpec.Invalid }) { - return - } - val tilesList = loadTiles(userId).toMutableList() - if (tilesList.removeAll(tiles)) { - storeTiles(userId, tilesList.toList()) + override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) = + mutex.withLock { + if (tiles.all { it == TileSpec.Invalid }) { + return + } + val tilesList = loadTiles(userId).toMutableList() + if (tilesList.removeAll(tiles)) { + storeTiles(userId, tilesList.toList()) + } } - } - override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) { - val filtered = tiles.filter { it != TileSpec.Invalid } - if (filtered.isNotEmpty()) { - storeTiles(userId, filtered) + override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) = + mutex.withLock { + val filtered = tiles.filter { it != TileSpec.Invalid } + if (filtered.isNotEmpty()) { + storeTiles(userId, filtered) + } } - } private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> { return withContext(backgroundDispatcher) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index c579f5c3061c..ff881f767b87 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -36,6 +36,7 @@ import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.TileLifecycleManager import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.domain.model.TileModel import com.android.systemui.qs.pipeline.shared.TileSpec @@ -52,6 +53,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn @@ -117,11 +120,13 @@ interface CurrentTilesInteractor : ProtoDumpable { * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch] * * [CustomTile]s will only be destroyed if the user changes. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class CurrentTilesInteractorImpl @Inject constructor( private val tileSpecRepository: TileSpecRepository, + private val installedTilesComponentRepository: InstalledTilesComponentRepository, private val userRepository: UserRepository, private val customTileStatePersister: CustomTileStatePersister, private val tileFactory: QSFactory, @@ -141,7 +146,7 @@ constructor( override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow() // This variable should only be accessed inside the collect of `startTileCollection`. - private val specsToTiles = mutableMapOf<TileSpec, QSTile>() + private val specsToTiles = mutableMapOf<TileSpec, TileOrNotInstalled>() private val currentUser = MutableStateFlow(userTracker.userId) override val userId = currentUser.asStateFlow() @@ -149,6 +154,20 @@ constructor( private val _userContext = MutableStateFlow(userTracker.userContext) override val userContext = _userContext.asStateFlow() + private val userAndTiles = + currentUser + .flatMapLatest { userId -> + tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) } + } + .distinctUntilChanged() + .pairwise(UserAndTiles(-1, emptyList())) + .flowOn(backgroundDispatcher) + + private val installedPackagesWithTiles = + currentUser.flatMapLatest { + installedTilesComponentRepository.getInstalledTilesComponents(it) + } + init { if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { startTileCollection() @@ -158,68 +177,98 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) private fun startTileCollection() { scope.launch { - userRepository.selectedUserInfo - .flatMapLatest { user -> + launch { + userRepository.selectedUserInfo.collect { user -> currentUser.value = user.id _userContext.value = userTracker.userContext - tileSpecRepository.tilesSpecs(user.id).map { user.id to it } } - .distinctUntilChanged() - .pairwise(-1 to emptyList()) - .flowOn(backgroundDispatcher) - .collect { (old, new) -> - val newTileList = new.second - val userChanged = old.first != new.first - val newUser = new.first - - // Destroy all tiles that are not in the new set - specsToTiles - .filter { it.key !in newTileList } - .forEach { entry -> - logger.logTileDestroyed( - entry.key, - if (userChanged) { - QSPipelineLogger.TileDestroyedReason - .TILE_NOT_PRESENT_IN_NEW_USER - } else { - QSPipelineLogger.TileDestroyedReason.TILE_REMOVED - } - ) - entry.value.destroy() - } - // MutableMap will keep the insertion order - val newTileMap = mutableMapOf<TileSpec, QSTile>() - - newTileList.forEach { tileSpec -> - if (tileSpec !in newTileMap) { - val newTile = - if (tileSpec in specsToTiles) { - processExistingTile( - tileSpec, - specsToTiles.getValue(tileSpec), - userChanged, - newUser - ) - ?: createTile(tileSpec) + } + + launch(backgroundDispatcher) { + userAndTiles + .combine(installedPackagesWithTiles) { usersAndTiles, packages -> + Data( + usersAndTiles.previousValue, + usersAndTiles.newValue, + packages, + ) + } + .collectLatest { + val newTileList = it.newData.tiles + val userChanged = it.oldData.userId != it.newData.userId + val newUser = it.newData.userId + val components = it.installedComponents + + // Destroy all tiles that are not in the new set + specsToTiles + .filter { + it.key !in newTileList && it.value is TileOrNotInstalled.Tile + } + .forEach { entry -> + logger.logTileDestroyed( + entry.key, + if (userChanged) { + QSPipelineLogger.TileDestroyedReason + .TILE_NOT_PRESENT_IN_NEW_USER + } else { + QSPipelineLogger.TileDestroyedReason.TILE_REMOVED + } + ) + (entry.value as TileOrNotInstalled.Tile).tile.destroy() + } + // MutableMap will keep the insertion order + val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>() + + newTileList.forEach { tileSpec -> + if (tileSpec !in newTileMap) { + if ( + tileSpec is TileSpec.CustomTileSpec && + tileSpec.componentName !in components + ) { + newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled } else { - createTile(tileSpec) + // Create tile here will never try to create a CustomTile that + // is not installed + val newTile = + if (tileSpec in specsToTiles) { + processExistingTile( + tileSpec, + specsToTiles.getValue(tileSpec), + userChanged, + newUser + ) + ?: createTile(tileSpec) + } else { + createTile(tileSpec) + } + if (newTile != null) { + newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile) + } } - if (newTile != null) { - newTileMap[tileSpec] = newTile } } - } - val resolvedSpecs = newTileMap.keys.toList() - specsToTiles.clear() - specsToTiles.putAll(newTileMap) - _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) } - if (resolvedSpecs != newTileList) { - // There were some tiles that couldn't be created. Change the value in the - // repository - launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) } + val resolvedSpecs = newTileMap.keys.toList() + specsToTiles.clear() + specsToTiles.putAll(newTileMap) + _currentSpecsAndTiles.value = + newTileMap + .filter { it.value is TileOrNotInstalled.Tile } + .map { + TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile) + } + logger.logTilesNotInstalled( + newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys, + newUser + ) + if (resolvedSpecs != newTileList) { + // There were some tiles that couldn't be created. Change the value in + // the + // repository + launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) } + } } - } + } } } @@ -301,42 +350,66 @@ constructor( private fun processExistingTile( tileSpec: TileSpec, - qsTile: QSTile, + tileOrNotInstalled: TileOrNotInstalled, userChanged: Boolean, user: Int, ): QSTile? { - return when { - !qsTile.isAvailable -> { - logger.logTileDestroyed( - tileSpec, - QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE - ) - qsTile.destroy() - null - } - // Tile is in the current list of tiles and available. - // We have a handful of different cases - qsTile !is CustomTile -> { - // The tile is not a custom tile. Make sure they are reset to the correct user - if (userChanged) { - qsTile.userSwitch(user) - logger.logTileUserChanged(tileSpec, user) + return when (tileOrNotInstalled) { + is TileOrNotInstalled.NotInstalled -> null + is TileOrNotInstalled.Tile -> { + val qsTile = tileOrNotInstalled.tile + when { + !qsTile.isAvailable -> { + logger.logTileDestroyed( + tileSpec, + QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE + ) + qsTile.destroy() + null + } + // Tile is in the current list of tiles and available. + // We have a handful of different cases + qsTile !is CustomTile -> { + // The tile is not a custom tile. Make sure they are reset to the correct + // user + if (userChanged) { + qsTile.userSwitch(user) + logger.logTileUserChanged(tileSpec, user) + } + qsTile + } + qsTile.user == user -> { + // The tile is a custom tile for the same user, just return it + qsTile + } + else -> { + // The tile is a custom tile and the user has changed. Destroy it + qsTile.destroy() + logger.logTileDestroyed( + tileSpec, + QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED + ) + null + } } - qsTile - } - qsTile.user == user -> { - // The tile is a custom tile for the same user, just return it - qsTile - } - else -> { - // The tile is a custom tile and the user has changed. Destroy it - qsTile.destroy() - logger.logTileDestroyed( - tileSpec, - QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED - ) - null } } } + + private sealed interface TileOrNotInstalled { + object NotInstalled : TileOrNotInstalled + + @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled + } + + private data class UserAndTiles( + val userId: Int, + val tiles: List<TileSpec>, + ) + + private data class Data( + val oldData: UserAndTiles, + val newData: UserAndTiles, + val installedComponents: Set<ComponentName>, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index ff7d2068bc4e..8318ec99e530 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -124,6 +124,18 @@ constructor( tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" }) } + fun logTilesNotInstalled(tiles: Collection<TileSpec>, user: Int) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.DEBUG, + { + str1 = tiles.toString() + int1 = user + }, + { "Tiles kept for not installed packages for user $int1: $str1" } + ) + } + /** Reasons for destroying an existing tile. */ enum class TileDestroyedReason(val readable: String) { TILE_REMOVED("Tile removed from current set"), diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index f080d3dfab1d..3af75cef3d4c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -34,6 +34,7 @@ import android.view.WindowInsets import android.widget.TextView import androidx.annotation.VisibleForTesting import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.view.doOnLayout import com.android.app.animation.Interpolators import com.android.settingslib.Utils import com.android.systemui.Dumpable @@ -220,6 +221,7 @@ constructor( override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK) override fun dispatchDemoCommand(command: String, args: Bundle) = clock.dispatchDemoCommand(command, args) + override fun onDemoModeStarted() = clock.onDemoModeStarted() override fun onDemoModeFinished() = clock.onDemoModeFinished() } @@ -259,6 +261,7 @@ constructor( resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) lastInsets?.let { updateConstraintsForInsets(header, it) } updateResources() + updateCarrierGroupPadding() } } @@ -291,6 +294,7 @@ constructor( privacyIconsController.chipVisibilityListener = chipVisibilityListener updateVisibility() updateTransition() + updateCarrierGroupPadding() header.setOnApplyWindowInsetsListener(insetListener) @@ -298,8 +302,6 @@ constructor( val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f v.pivotX = newPivot v.pivotY = v.height.toFloat() / 2 - - mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0) } clock.setOnClickListener { launchClockActivity() } @@ -359,6 +361,14 @@ constructor( .load(context, resources.getXml(R.xml.large_screen_shade_header)) } + private fun updateCarrierGroupPadding() { + clock.doOnLayout { + val maxClockWidth = + (clock.width * resources.getFloat(R.dimen.qqs_expand_clock_scale)).toInt() + mShadeCarrierGroup.setPaddingRelative(maxClockWidth, 0, 0, 0) + } + } + private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) { val cutout = insets.displayCutout.also { this.cutout = it } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt new file mode 100644 index 000000000000..891ee0cf66d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt @@ -0,0 +1,33 @@ +/* + * 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.kotlin + +import android.annotation.WorkerThread +import android.content.pm.ComponentInfo +import android.content.pm.PackageManager +import com.android.systemui.util.Assert + +@WorkerThread +fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean { + Assert.isNotMainThread() + return when (getComponentEnabledSetting(componentInfo.componentName)) { + PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true + PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false + PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> componentInfo.isEnabled + else -> false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index b24a69292186..b848d2e84faf 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -2006,14 +2006,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, if (row.anim == null) { row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress); row.anim.setInterpolator(new DecelerateInterpolator()); + row.anim.addListener( + getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION)); } else { row.anim.cancel(); row.anim.setIntValues(progress, newProgress); } row.animTargetProgress = newProgress; row.anim.setDuration(UPDATE_ANIMATION_DURATION); - row.anim.addListener( - getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION)); row.anim.start(); } else { // update slider directly to clamped value diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index 111b8e83a984..d36e77889810 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -92,8 +92,8 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun affordance_walletNotEnabled_modelIsNone() = runBlockingTest { - setUpState(isWalletEnabled = false) + fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest { + setUpState(isWalletFeatureAvailable = false) var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this) @@ -165,7 +165,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest { setUpState( - isWalletEnabled = false, + isWalletFeatureAvailable = false, ) assertThat(underTest.getPickerScreenState()) @@ -183,16 +183,15 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } private fun setUpState( - isWalletEnabled: Boolean = true, + isWalletFeatureAvailable: Boolean = true, isWalletServiceAvailable: Boolean = true, isWalletQuerySuccessful: Boolean = true, hasSelectedCard: Boolean = true, ) { - whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled) - val walletClient: QuickAccessWalletClient = mock() whenever(walletClient.tileIcon).thenReturn(ICON) whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable) + whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable) whenever(walletController.walletClient).thenReturn(walletClient) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 205fa0f11a26..9dba9b5b3c3e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -35,6 +35,8 @@ import android.media.session.MediaSessionManager; import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.View; +import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; @@ -58,6 +60,8 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.google.common.base.Strings; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -252,4 +256,106 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST); assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0); } + + @Test + public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() { + ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_name_edit); + TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById( + R.id.broadcast_name_summary); + broadcastName.setText(BROADCAST_NAME_TEST); + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + broadcastNameEdit.callOnClick(); + EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_edit_text); + TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_error_message); + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE); + + // input the invalid text + String moreThanMax = Strings.repeat("a", + MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3); + editText.setText(moreThanMax); + + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() { + ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_name_edit); + TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById( + R.id.broadcast_name_summary); + broadcastName.setText(BROADCAST_NAME_TEST); + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + broadcastNameEdit.callOnClick(); + EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_edit_text); + TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_error_message); + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE); + + // input the invalid text + String testString = Strings.repeat("a", + MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2); + editText.setText(testString); + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE); + + // input the valid text + testString = Strings.repeat("b", + MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100); + editText.setText(testString); + + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test + public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() { + ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_code_edit); + TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById( + R.id.broadcast_code_summary); + broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST); + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + broadcastCodeEdit.callOnClick(); + EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_edit_text); + TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_error_message); + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE); + + // input the invalid text + String moreThanMax = Strings.repeat("a", + MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1); + editText.setText(moreThanMax); + + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() { + ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_code_edit); + TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById( + R.id.broadcast_code_summary); + broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST); + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + broadcastCodeEdit.callOnClick(); + EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_edit_text); + TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById( + R.id.broadcast_error_message); + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE); + + // input the invalid text + String moreThanMax = Strings.repeat("a", + MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1); + editText.setText(moreThanMax); + + assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt new file mode 100644 index 000000000000..18f3837a7d36 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt @@ -0,0 +1,278 @@ +/* + * 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.qs.pipeline.data.repository + +import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.ResolveInfoFlags +import android.content.pm.ResolveInfo +import android.content.pm.ServiceInfo +import android.os.UserHandle +import android.service.quicksettings.TileService +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +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.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) +class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Mock private lateinit var context: Context + @Mock private lateinit var packageManager: PackageManager + @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver> + + private lateinit var underTest: InstalledTilesComponentRepositoryImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // Use the default value set in the ServiceInfo + whenever(packageManager.getComponentEnabledSetting(any())) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + // Return empty by default + whenever(packageManager.queryIntentServicesAsUser(any(), any<ResolveInfoFlags>(), anyInt())) + .thenReturn(emptyList()) + + underTest = + InstalledTilesComponentRepositoryImpl( + context, + packageManager, + testDispatcher, + ) + } + + @Test + fun registersAndUnregistersBroadcastReceiver() = + testScope.runTest { + val user = 10 + val job = launch { underTest.getInstalledTilesComponents(user).collect {} } + runCurrent() + + verify(context) + .registerReceiverAsUser( + capture(receiverCaptor), + eq(UserHandle.of(user)), + any(), + nullable(), + nullable(), + ) + + verify(context, never()).unregisterReceiver(receiverCaptor.value) + + job.cancel() + runCurrent() + verify(context).unregisterReceiver(receiverCaptor.value) + } + + @Test + fun intentFilterForCorrectActionsAndScheme() = + testScope.runTest { + val filterCaptor = argumentCaptor<IntentFilter>() + + backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} } + runCurrent() + + verify(context) + .registerReceiverAsUser( + any(), + any(), + capture(filterCaptor), + nullable(), + nullable(), + ) + + with(filterCaptor.value) { + assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue() + assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue() + assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue() + assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue() + assertThat(countActions()).isEqualTo(4) + + assertThat(hasDataScheme("package")).isTrue() + assertThat(countDataSchemes()).isEqualTo(1) + } + } + + @Test + fun componentsLoadedOnStart() = + testScope.runTest { + val userId = 0 + val resolveInfo = + ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true) + whenever( + packageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + + val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + + assertThat(componentNames).containsExactly(TEST_COMPONENT) + } + + @Test + fun componentAdded_foundAfterBroadcast() = + testScope.runTest { + val userId = 0 + val resolveInfo = + ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true) + + val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + assertThat(componentNames).isEmpty() + + whenever( + packageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED)) + + assertThat(componentNames).containsExactly(TEST_COMPONENT) + } + + @Test + fun componentWithoutPermission_notValid() = + testScope.runTest { + val userId = 0 + val resolveInfo = + ResolveInfo(TEST_COMPONENT, hasPermission = false, defaultEnabled = true) + whenever( + packageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + + val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + assertThat(componentNames).isEmpty() + } + + @Test + fun componentNotEnabled_notValid() = + testScope.runTest { + val userId = 0 + val resolveInfo = + ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = false) + whenever( + packageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + + val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + assertThat(componentNames).isEmpty() + } + + private fun getRegisteredReceiver(): BroadcastReceiver { + verify(context) + .registerReceiverAsUser( + capture(receiverCaptor), + any(), + any(), + nullable(), + nullable(), + ) + + return receiverCaptor.value + } + + companion object { + private val INTENT = Intent(TileService.ACTION_QS_TILE) + private val FLAGS = + ResolveInfoFlags.of( + (PackageManager.MATCH_DIRECT_BOOT_AWARE or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE or + PackageManager.GET_SERVICES) + .toLong() + ) + private val PERMISSION = BIND_QUICK_SETTINGS_TILE + + private val TEST_COMPONENT = ComponentName("pkg", "cls") + + private fun matchFlags() = + argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value } + private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action } + + private fun ResolveInfo( + componentName: ComponentName, + hasPermission: Boolean, + defaultEnabled: Boolean + ): ResolveInfo { + val applicationInfo = ApplicationInfo().apply { enabled = true } + val serviceInfo = + ServiceInfo().apply { + packageName = componentName.packageName + name = componentName.className + if (hasPermission) { + permission = PERMISSION + } + enabled = defaultEnabled + this.applicationInfo = applicationInfo + } + val resolveInfo = ResolveInfo() + resolveInfo.serviceInfo = serviceInfo + return resolveInfo + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 426ff670802f..e7ad4896810b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.qs.external.TileLifecycleManager import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.domain.model.TileModel @@ -73,6 +74,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository() private val userRepository = FakeUserRepository() + private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository() private val tileFactory = FakeQSFactory(::tileCreator) private val customTileAddedRepository: CustomTileAddedRepository = FakeCustomTileAddedRepository() @@ -100,11 +102,13 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true) userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1)) + setUserTracker(0) underTest = CurrentTilesInteractorImpl( tileSpecRepository = tileSpecRepository, + installedTilesComponentRepository = installedTilesPackageRepository, userRepository = userRepository, customTileStatePersister = customTileStatePersister, tileFactory = tileFactory, @@ -609,6 +613,40 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback) } + @Test + fun packageNotInstalled_customTileNotVisible() = + testScope.runTest(USER_INFO_0) { + installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet()) + + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + assertThat(tiles!!.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(specs[0]) + } + + @Test + fun packageInstalledLater_customTileAdded() = + testScope.runTest(USER_INFO_0) { + installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet()) + + val tiles by collectLastValue(underTest.currentTiles) + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + assertThat(tiles!!.size).isEqualTo(2) + + installedTilesPackageRepository.setInstalledPackagesForUser( + USER_INFO_0.id, + setOf(TEST_COMPONENT) + ) + + assertThat(tiles!!.size).isEqualTo(3) + assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC) + } + private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { this.state = state this.label = label @@ -654,6 +692,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private suspend fun switchUser(user: UserInfo) { setUserTracker(user.id) + installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT)) userRepository.setSelectedUserInfo(user) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index 20da8a619100..2da2e9238d0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -78,6 +78,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.reset +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @@ -387,7 +388,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { whenever(clock.isLayoutRtl).thenReturn(false) val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) - verify(clock).addOnLayoutChangeListener(capture(captor)) + verify(clock, times(2)).addOnLayoutChangeListener(capture(captor)) captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7) verify(clock).pivotX = 0f @@ -400,7 +401,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { whenever(clock.isLayoutRtl).thenReturn(true) val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) - verify(clock).addOnLayoutChangeListener(capture(captor)) + verify(clock, times(2)).addOnLayoutChangeListener(capture(captor)) captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7) verify(clock).pivotX = width.toFloat() @@ -793,7 +794,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Test fun clockPivotYInCenter() { val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) - verify(clock).addOnLayoutChangeListener(capture(captor)) + verify(clock, times(2)).addOnLayoutChangeListener(capture(captor)) var height = 100 val width = 50 @@ -825,16 +826,17 @@ class ShadeHeaderControllerTest : SysuiTestCase() { } @Test - fun carrierLeftPaddingIsSetWhenClockLayoutChanges() { - val width = 200 - whenever(clock.width).thenReturn(width) - whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml - val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) + fun carrierStartPaddingIsSetOnClockLayout() { + val clockWidth = 200 + val maxClockScale = context.resources.getFloat(R.dimen.qqs_expand_clock_scale) + val expectedStartPadding = (clockWidth * maxClockScale).toInt() + whenever(clock.width).thenReturn(clockWidth) - verify(clock).addOnLayoutChangeListener(capture(captor)) - captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0) + val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java) + verify(clock, times(2)).addOnLayoutChangeListener(capture(captor)) + captor.allValues.forEach { clock.executeLayoutChange(0, 0, clockWidth, 0, it) } - verify(carrierGroup).setPaddingRelative(514, 0, 0, 0) + verify(carrierGroup).setPaddingRelative(expectedStartPadding, 0, 0, 0) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt new file mode 100644 index 000000000000..2013bb0a547e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt @@ -0,0 +1,153 @@ +/* + * 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.kotlin + +import android.content.ComponentName +import android.content.pm.ComponentInfo +import android.content.pm.PackageManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(Parameterized::class) +internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) : + SysuiTestCase() { + + @Mock private lateinit var packageManager: PackageManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testComponentActuallyEnabled() { + whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT)) + .thenReturn(testCase.componentEnabledSetting) + val componentInfo = + mock<ComponentInfo>() { + whenever(isEnabled).thenReturn(testCase.componentIsEnabled) + whenever(componentName).thenReturn(TEST_COMPONENT) + } + + assertThat(packageManager.isComponentActuallyEnabled(componentInfo)) + .isEqualTo(testCase.expected) + } + + internal data class TestCase( + @PackageManager.EnabledState val componentEnabledSetting: Int, + val componentIsEnabled: Boolean, + val expected: Boolean, + ) { + override fun toString(): String { + return "WHEN(" + + "componentIsEnabled = $componentIsEnabled, " + + "componentEnabledSetting = ${enabledStateToString()}) then " + + "EXPECTED = $expected" + } + + private fun enabledStateToString() = + when (componentEnabledSetting) { + PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> "STATE_DEFAULT" + PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> "STATE_DISABLED" + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED -> { + "STATE_DISABLED_UNTIL_USED" + } + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> "STATE_DISABLED_USER" + PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> "STATE_ENABLED" + else -> "INVALID STATE" + } + } + + companion object { + @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData + + private val testDataComponentIsEnabled = + listOf( + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + componentIsEnabled = true, + expected = true, + ), + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, + componentIsEnabled = true, + expected = false, + ), + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + componentIsEnabled = true, + expected = false, + ), + TestCase( + componentEnabledSetting = + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, + componentIsEnabled = true, + expected = false, + ), + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, + componentIsEnabled = true, + expected = true, + ), + ) + + private val testDataComponentIsDisabled = + listOf( + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + componentIsEnabled = false, + expected = true, + ), + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, + componentIsEnabled = false, + expected = false, + ), + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + componentIsEnabled = false, + expected = false, + ), + TestCase( + componentEnabledSetting = + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, + componentIsEnabled = false, + expected = false, + ), + TestCase( + componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, + componentIsEnabled = false, + expected = false, + ), + ) + + private val testData = testDataComponentIsDisabled + testDataComponentIsEnabled + + private val TEST_COMPONENT = ComponentName("pkg", "cls") + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt new file mode 100644 index 000000000000..ff6b7d083df7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt @@ -0,0 +1,39 @@ +/* + * 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.qs.pipeline.data.repository + +import android.content.ComponentName +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository { + + private val installedComponentsPerUser = + mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>() + + override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> { + return getFlow(userId).asStateFlow() + } + + fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) { + getFlow(userId).value = components + } + + private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> = + installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) } +} diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 0b5b1cb2902e..4b6d32427d68 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -25,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; +import android.app.BroadcastOptions; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.SystemClock; @@ -257,7 +258,10 @@ class BroadcastProcessQueue { deferredStatesApplyConsumer.accept(record, recordIndex); } - if (record.isReplacePending()) { + // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the + // BroadcastOptions delivery group APIs. + if (record.isReplacePending() + && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) { final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex); if (replacedBroadcastRecord != null) { return replacedBroadcastRecord; diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index f6004d7d2b7f..c6165cd3220e 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -339,7 +339,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue, BroadcastRecord r, String typeForLogging) { final Intent intent = r.intent; - for (int i = queue.size() - 1; i > 0; i--) { + for (int i = queue.size() - 1; i >= 0; i--) { final BroadcastRecord old = queue.get(i); if (old.userId == r.userId && intent.filterEquals(old.intent)) { if (DEBUG_BROADCAST) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index cbc75401f0b8..93fb91ab8cdd 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -719,11 +719,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) { for (int i = 0; i < replacedBroadcasts.size(); ++i) { final BroadcastRecord r = replacedBroadcasts.valueAt(i); - r.resultCode = Activity.RESULT_CANCELED; - r.resultData = null; - r.resultExtras = null; - scheduleResultTo(r); - notifyFinishBroadcast(r); + // Skip all the receivers in the replaced broadcast + for (int rcvrIdx = 0; rcvrIdx < r.receivers.size(); ++rcvrIdx) { + if (!isDeliveryStateTerminal(r.getDeliveryState(rcvrIdx))) { + mBroadcastConsumerSkipAndCanceled.accept(r, rcvrIdx); + } + } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d0b6cdce037f..336f0fb5699f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3277,6 +3277,7 @@ public class AudioService extends IAudioService.Stub if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1 streamType = mVolumeControlStream; } else { + // TODO discard activity on a muted stream? final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType); final boolean activeForReal; if (maybeActiveStreamType == AudioSystem.STREAM_RING @@ -3480,9 +3481,10 @@ public class AudioService extends IAudioService.Stub } } else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) { // if the stream is currently muted streams by ringer/zen mode - // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES) + // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES) with an unmute or raise if (direction == AudioManager.ADJUST_TOGGLE_MUTE - || direction == AudioManager.ADJUST_UNMUTE) { + || direction == AudioManager.ADJUST_UNMUTE + || direction == AudioManager.ADJUST_RAISE) { adjustVolume = false; } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index aeff2b0f9af6..b39e8606e189 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING; +import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; @@ -223,6 +224,8 @@ import android.os.IInterface; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -234,6 +237,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.VibrationEffect; +import android.os.WorkSource; import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.Settings; @@ -559,6 +563,7 @@ public class NotificationManagerService extends SystemService { private PermissionHelper mPermissionHelper; private UsageStatsManagerInternal mUsageStatsManagerInternal; private TelecomManager mTelecomManager; + private PowerManager mPowerManager; private PostNotificationTrackerFactory mPostNotificationTrackerFactory; final IBinder mForegroundToken = new Binder(); @@ -923,7 +928,7 @@ public class NotificationManagerService extends SystemService { if (oldFlags != flags) { summary.getSbn().getNotification().flags = flags; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, - mPostNotificationTrackerFactory.newTracker())); + mPostNotificationTrackerFactory.newTracker(null))); } } @@ -1457,7 +1462,7 @@ public class NotificationManagerService extends SystemService { // want to adjust the flag behaviour. mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, true /* isAppForeground*/, - mPostNotificationTrackerFactory.newTracker())); + mPostNotificationTrackerFactory.newTracker(null))); } } } @@ -1488,7 +1493,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, /* foreground= */ true, - mPostNotificationTrackerFactory.newTracker())); + mPostNotificationTrackerFactory.newTracker(null))); } } } @@ -2233,7 +2238,7 @@ public class NotificationManagerService extends SystemService { UsageStatsManagerInternal usageStatsManagerInternal, TelecomManager telecomManager, NotificationChannelLogger channelLogger, SystemUiSystemPropertiesFlags.FlagResolver flagResolver, - PermissionManager permissionManager, + PermissionManager permissionManager, PowerManager powerManager, PostNotificationTrackerFactory postNotificationTrackerFactory) { mHandler = handler; Resources resources = getContext().getResources(); @@ -2265,6 +2270,7 @@ public class NotificationManagerService extends SystemService { mDpm = dpm; mUm = userManager; mTelecomManager = telecomManager; + mPowerManager = powerManager; mPostNotificationTrackerFactory = postNotificationTrackerFactory; mPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); @@ -2568,6 +2574,7 @@ public class NotificationManagerService extends SystemService { getContext().getSystemService(TelecomManager.class), new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(), getContext().getSystemService(PermissionManager.class), + getContext().getSystemService(PowerManager.class), new PostNotificationTrackerFactory() {}); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, @@ -2684,7 +2691,7 @@ public class NotificationManagerService extends SystemService { final boolean isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, - mPostNotificationTrackerFactory.newTracker())); + mPostNotificationTrackerFactory.newTracker(null))); } } @@ -6577,7 +6584,7 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently) { - PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(); + PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid); boolean enqueued = false; try { enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, @@ -6589,6 +6596,22 @@ public class NotificationManagerService extends SystemService { } } + private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) { + if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) { + // The package probably doesn't have WAKE_LOCK permission and should not require it. + return Binder.withCleanCallingIdentity(() -> { + WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "NotificationManagerService:post:" + pkg); + wakeLock.setWorkSource(new WorkSource(uid, pkg)); + // TODO(b/275044361): Adjust to a more reasonable number when we have the data. + wakeLock.acquire(30_000); + return mPostNotificationTrackerFactory.newTracker(wakeLock); + }); + } else { + return mPostNotificationTrackerFactory.newTracker(null); + } + } + /** * @return True if we successfully processed the notification and handed off the task of * enqueueing it to a background thread; false otherwise. @@ -7106,7 +7129,7 @@ public class NotificationManagerService extends SystemService { mHandler.post( new NotificationManagerService.EnqueueNotificationRunnable( r.getUser().getIdentifier(), r, isAppForeground, - mPostNotificationTrackerFactory.newTracker())); + mPostNotificationTrackerFactory.newTracker(null))); } } } @@ -12168,20 +12191,20 @@ public class NotificationManagerService extends SystemService { } interface PostNotificationTrackerFactory { - default PostNotificationTracker newTracker() { - return new PostNotificationTracker(); + default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) { + return new PostNotificationTracker(optionalWakelock); } } static class PostNotificationTracker { @ElapsedRealtimeLong private final long mStartTime; - @Nullable private NotificationRecordLogger.NotificationReported mReport; + @Nullable private final WakeLock mWakeLock; private boolean mOngoing; @VisibleForTesting - PostNotificationTracker() { - // TODO(b/275044361): (Conditionally) receive a wakelock. + PostNotificationTracker(@Nullable WakeLock wakeLock) { mStartTime = SystemClock.elapsedRealtime(); + mWakeLock = wakeLock; mOngoing = true; if (DBG) { Slog.d(TAG, "PostNotification: Started"); @@ -12199,9 +12222,8 @@ public class NotificationManagerService extends SystemService { } /** - * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either - * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before - * it's discarded. + * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or + * {@link #cancel} (exclusively) should be called on this object before it's discarded. */ void cancel() { if (!isOngoing()) { @@ -12209,9 +12231,9 @@ public class NotificationManagerService extends SystemService { return; } mOngoing = false; - - // TODO(b/275044361): Release wakelock. - + if (mWakeLock != null) { + Binder.withCleanCallingIdentity(() -> mWakeLock.release()); + } if (DBG) { long elapsedTime = SystemClock.elapsedRealtime() - mStartTime; Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms", @@ -12220,9 +12242,9 @@ public class NotificationManagerService extends SystemService { } /** - * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the - * time elapsed since the operation started, in milliseconds. Either {@link #finish} or - * {@link #cancel} (exclusively) should be called on this object before it's discarded. + * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since + * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel} + * (exclusively) should be called on this object before it's discarded. */ @DurationMillisLong long finish() { @@ -12232,9 +12254,9 @@ public class NotificationManagerService extends SystemService { return elapsedTime; } mOngoing = false; - - // TODO(b/275044361): Release wakelock. - + if (mWakeLock != null) { + Binder.withCleanCallingIdentity(() -> mWakeLock.release()); + } if (DBG) { Slog.d(TAG, TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime)); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 06db5be349d4..50f1673cae44 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2145,7 +2145,7 @@ final class InstallPackageHelper { final String pkgName = pkg.getPackageName(); final int[] installedForUsers = installRequest.getOriginUsers(); final int installReason = installRequest.getInstallReason(); - final String installerPackageName = installRequest.getSourceInstallerPackageName(); + final String installerPackageName = installRequest.getInstallerPackageName(); if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath()); synchronized (mPm.mLock) { diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 95e790450724..34648740d54c 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -366,12 +366,6 @@ final class InstallRequest { public String getApexModuleName() { return mApexModuleName; } - - @Nullable - public String getSourceInstallerPackageName() { - return mInstallArgs.mInstallSource.mInstallerPackageName; - } - public boolean isRollback() { return mInstallArgs != null && mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 6491fd1b1f98..a9115371413c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -511,7 +511,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } catch (FileNotFoundException e) { // Missing sessions are okay, probably first boot - } catch (IOException | XmlPullParserException e) { + } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) { Slog.wtf(TAG, "Failed reading install sessions", e); } finally { IoUtils.closeQuietly(fis); diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java index 19aa4f8e8d0b..54ca426a6dc3 100644 --- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java +++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java @@ -230,7 +230,9 @@ final class ResilientAtomicFile implements Closeable { + Log.getStackTraceString(e)); } - mCurrentFile.delete(); + if (!mCurrentFile.delete()) { + throw new IllegalStateException("Failed to remove " + mCurrentFile); + } mCurrentFile = null; } diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d108e1487564..d55f85cde5af 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -986,11 +986,14 @@ public class PackageInfoUtils { } /** @see ApplicationInfo#privateFlagsExt */ - public static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags, + private static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags, @Nullable PackageStateInternal pkgSetting) { // @formatter:off - // TODO: Add state specific flags - return pkgWithoutStateFlags; + int flags = pkgWithoutStateFlags; + if (pkgSetting != null) { + flags |= flag(pkgSetting.getCpuAbiOverride() != null, ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE); + } + return flags; // @formatter:on } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2b2100e56f44..9f16a8441533 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4281,6 +4281,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); mTaskSupervisor.mStoppingActivities.remove(this); + mLetterboxUiController.destroy(); waitingToShow = false; // Defer removal of this activity when either a child is animating, or app transition is on @@ -4350,8 +4351,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); } - mLetterboxUiController.destroy(); - if (!delayed) { updateReportedVisibilityLocked(); } diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index 123a74dbf597..f7ccc0d91969 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -23,6 +23,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.os.Build; import android.os.Handler; @@ -178,6 +179,14 @@ class AppWarnings { * @param r activity record for which the warning may be displayed */ public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) { + final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt + & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0; + if (isUsingAbiOverride) { + // The abiOverride flag was specified during installation, which means that if the app + // is currently running in 32-bit mode, it is intended. Do not show the warning dialog. + return; + } + // The warning dialog can also be disabled for debugging purpose final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean( "debug.wm.disable_deprecated_abi_dialog", false); if (disableDeprecatedAbiDialog) { diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java index 31b1069dd022..db4762e5f877 100644 --- a/services/core/java/com/android/server/wm/DeviceStateController.java +++ b/services/core/java/com/android/server/wm/DeviceStateController.java @@ -111,9 +111,13 @@ final class DeviceStateController { } /** - * @return true if the rotation direction on the Z axis should be reversed. + * @return true if the rotation direction on the Z axis should be reversed for the default + * display. */ - boolean shouldReverseRotationDirectionAroundZAxis() { + boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) { + if (!displayContent.isDefaultDisplay) { + return false; + } return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 8be36f07a040..b681c198538f 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -950,7 +950,7 @@ public class DisplayRotation { } void freezeRotation(int rotation) { - if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) { + if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) { rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation); } @@ -1225,7 +1225,7 @@ public class DisplayRotation { if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) { sensorRotation = -1; } - if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) { + if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) { sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation); } mLastSensorRotation = sensorRotation; diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 774f62d44045..fd478dc12c13 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -31,6 +31,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -245,10 +246,13 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + ActivityOptions activityOptions = ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED); IntentSender intentSender = PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, - null, new UserHandle(mUserId)) .getIntentSender(); + | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + activityOptions.toBundle(), new UserHandle(mUserId)).getIntentSender(); Bundle result = new Bundle(); result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java index dc92376263a6..ca4a4048cc00 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java @@ -66,6 +66,7 @@ import android.system.StructStat; import android.test.AndroidTestCase; import android.util.Log; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; @@ -2506,6 +2507,7 @@ public class PackageManagerTests extends AndroidTestCase { } @LargeTest + @FlakyTest(bugId = 283797480) public void testCheckSignaturesRotatedAgainstRotated() throws Exception { // checkSignatures should be successful when both apps have been signed with the same // rotated key since the initial signature comparison between the two apps should diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 4989f841a275..03231ecfb723 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -1904,6 +1904,34 @@ public class BroadcastQueueTest { } @Test + public void testReplacePending_diffReceivers() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); + final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp); + final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp); + final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of( + withPriority(receiverGreen, 10), + withPriority(receiverBlue, 5), + withPriority(receiverYellow, 0)))); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of( + withPriority(receiverGreen, 10), + withPriority(receiverBlue, 5)))); + + waitForIdle(); + + verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); + verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane); + verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane); + } + + @Test public void testIdleAndBarrier() throws Exception { final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index f44c1d18614d..4315254f68a9 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" /> @@ -36,6 +37,7 @@ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" /> <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9166b3d75f60..2a0c745a0ffc 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; +import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; @@ -80,6 +81,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; +import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; @@ -119,12 +121,14 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import android.Manifest; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -181,11 +185,14 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.WorkSource; import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.MediaStore; @@ -351,6 +358,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private PermissionManager mPermissionManager; @Mock private DevicePolicyManagerInternal mDevicePolicyManager; + @Mock + private PowerManager mPowerManager; + private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>(); private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory = new TestPostNotificationTrackerFactory(); @@ -431,8 +441,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private final List<PostNotificationTracker> mCreatedTrackers = new ArrayList<>(); @Override - public PostNotificationTracker newTracker() { - PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker(); + public PostNotificationTracker newTracker(@Nullable WakeLock optionalWakeLock) { + PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker( + optionalWakeLock); mCreatedTrackers.add(tracker); return tracker; } @@ -563,6 +574,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true); + // Use the real PowerManager to back up the mock w.r.t. creating WakeLocks. + // This is because 1) we need a mock to verify() calls and tracking the created WakeLocks, + // but 2) PowerManager and WakeLock perform their own checks (e.g. correct arguments, don't + // call release twice, etc) and we want the test to fail if such misuse happens, too. + PowerManager realPowerManager = mContext.getSystemService(PowerManager.class); + when(mPowerManager.newWakeLock(anyInt(), anyString())).then( + (Answer<WakeLock>) invocation -> { + WakeLock wl = realPowerManager.newWakeLock(invocation.getArgument(0), + invocation.getArgument(1)); + mAcquiredWakeLocks.add(wl); + return wl; + }); + mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true); + // apps allowed as convos mService.setStringArrayResourceValue(PKG_O); @@ -579,7 +604,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class), mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager, - mPostNotificationTrackerFactory); + mPowerManager, mPostNotificationTrackerFactory); // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); @@ -686,6 +711,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @After + public void assertAllWakeLocksReleased() { + for (WakeLock wakeLock : mAcquiredWakeLocks) { + assertThat(wakeLock.isHeld()).isFalse(); + } + } + + @After public void tearDown() throws Exception { if (mFile != null) mFile.delete(); clearDeviceConfig(); @@ -1486,7 +1518,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -1507,7 +1539,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -1803,6 +1835,112 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception { + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "enqueueNotification_acquiresAndReleasesWakeLock", 0, + generateNotificationRecord(null).getNotification(), 0); + + verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString()); + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue(); + + waitForIdle(); + + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse(); + } + + @Test + public void enqueueNotification_throws_acquiresAndReleasesWakeLock() throws Exception { + // Simulate not enqueued due to rejected inputs. + assertThrows(Exception.class, + () -> mBinderService.enqueueNotificationWithTag(PKG, PKG, + "enqueueNotification_throws_acquiresAndReleasesWakeLock", 0, + /* notification= */ null, 0)); + + verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString()); + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse(); + } + + @Test + public void enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock() throws Exception { + // Simulate not enqueued due to snoozing inputs. + when(mSnoozeHelper.getSnoozeContextForUnpostedNotification(anyInt(), any(), any())) + .thenReturn("zzzzzzz"); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0, + generateNotificationRecord(null).getNotification(), 0); + + verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString()); + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue(); + + waitForIdle(); + + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse(); + } + + @Test + public void enqueueNotification_notPosted_acquiresAndReleasesWakeLock() throws Exception { + // Simulate enqueued but not posted due to missing small icon. + Notification notif = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .build(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0, + notif, 0); + + verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString()); + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue(); + + waitForIdle(); + + // NLSes were not called. + verify(mListeners, never()).prepareNotifyPostedLocked(any(), any(), anyBoolean()); + + assertThat(mAcquiredWakeLocks).hasSize(1); + assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse(); + } + + @Test + public void enqueueNotification_setsWakeLockWorkSource() throws Exception { + // Use a "full" mock for the PowerManager (instead of the one that delegates to the real + // service) so we can return a mocked WakeLock that we can verify() on. + reset(mPowerManager); + WakeLock wakeLock = mock(WakeLock.class); + when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(wakeLock); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "enqueueNotification_setsWakeLockWorkSource", 0, + generateNotificationRecord(null).getNotification(), 0); + waitForIdle(); + + InOrder inOrder = inOrder(mPowerManager, wakeLock); + inOrder.verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString()); + inOrder.verify(wakeLock).setWorkSource(eq(new WorkSource(mUid, PKG))); + inOrder.verify(wakeLock).acquire(anyLong()); + inOrder.verify(wakeLock).release(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception { + mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "enqueueNotification_setsWakeLockWorkSource", 0, + generateNotificationRecord(null).getNotification(), 0); + waitForIdle(); + + verifyZeroInteractions(mPowerManager); + } + + @Test public void testCancelNonexistentNotification() throws Exception { mBinderService.cancelNotificationWithTag(PKG, PKG, "testCancelNonexistentNotification", 0, 0); @@ -4361,7 +4499,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -4380,7 +4518,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(update.getKey(), update.getSbn().getPackageName(), update.getUid(), - mPostNotificationTrackerFactory.newTracker()); + mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -4400,7 +4538,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(update.getKey(), update.getSbn().getPackageName(), update.getUid(), - mPostNotificationTrackerFactory.newTracker()); + mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -4420,7 +4558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(update.getKey(), update.getSbn().getPackageName(), - update.getUid(), mPostNotificationTrackerFactory.newTracker()); + update.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -4434,13 +4572,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); r = generateNotificationRecord(mTestNotificationChannel, 1, null, false); r.setCriticality(CriticalNotificationExtractor.CRITICAL); runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); mService.addEnqueuedNotification(r); runnable.run(); @@ -5090,7 +5228,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.new PostNotificationRunnable(original.getKey(), original.getSbn().getPackageName(), original.getUid(), - mPostNotificationTrackerFactory.newTracker()); + mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -5114,7 +5252,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.new PostNotificationRunnable(update.getKey(), update.getSbn().getPackageName(), update.getUid(), - mPostNotificationTrackerFactory.newTracker()); + mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -7536,7 +7674,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -10234,7 +10372,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -10251,7 +10389,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -10268,7 +10406,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker()); + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)); runnable.run(); waitForIdle(); @@ -10361,7 +10499,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // normal blocked notifications - blocked mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats).registerBlocked(any()); @@ -10379,7 +10517,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats).registerBlocked(any()); @@ -10392,7 +10530,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats, never()).registerBlocked(any()); @@ -10406,7 +10544,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats, never()).registerBlocked(any()); @@ -10420,7 +10558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats).registerBlocked(any()); @@ -10435,7 +10573,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats).registerBlocked(any()); @@ -10448,7 +10586,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(), - mPostNotificationTrackerFactory.newTracker()).run(); + mPostNotificationTrackerFactory.newTracker(null)).run(); waitForIdle(); verify(mUsageStats).registerBlocked(any()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index 66c1e35754c5..81c573d8fb1e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -48,6 +48,7 @@ import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.os.Looper; +import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; import android.permission.PermissionManager; @@ -169,6 +170,7 @@ public class RoleObserverTest extends UiServiceTestCase { mock(UsageStatsManagerInternal.class), mock(TelecomManager.class), mock(NotificationChannelLogger.class), new TestableFlagResolver(), mock(PermissionManager.class), + mock(PowerManager.class), new NotificationManagerService.PostNotificationTrackerFactory() {}); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 04e1d9c07a07..2a8f0ffc4d49 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -59,10 +59,10 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.devicestate.DeviceStateManager; +import android.os.Handler; import android.os.IBinder; import android.os.PowerManagerInternal; import android.os.SystemClock; -import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.DisplayAddress; @@ -518,7 +518,8 @@ public class DisplayRotationTests { mBuilder.build(); configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false); - when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true); + when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent)) + .thenReturn(true); thawRotation(); @@ -544,7 +545,8 @@ public class DisplayRotationTests { @Test public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception { mBuilder.build(); - when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true); + when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent)) + .thenReturn(true); freezeRotation(Surface.ROTATION_90); assertEquals(Surface.ROTATION_270, mTarget.getUserRotation()); @@ -553,7 +555,8 @@ public class DisplayRotationTests { @Test public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception { mBuilder.build(); - when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false); + when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent)) + .thenReturn(false); freezeRotation(Surface.ROTATION_90); assertEquals(Surface.ROTATION_90, mTarget.getUserRotation()); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e74ad3364b63..082122e33175 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -406,7 +406,6 @@ public class SizeCompatTests extends WindowTestsBase { clearInvocations(translucentActivity.mLetterboxUiController); // We destroy the first opaque activity - mActivity.setState(DESTROYED, "testing"); mActivity.removeImmediately(); // Check that updateInheritedLetterbox() is invoked again diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml index bedf0990a188..bfebb42ad244 100644 --- a/tests/InputMethodStressTest/AndroidTest.xml +++ b/tests/InputMethodStressTest/AndroidTest.xml @@ -19,6 +19,7 @@ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" /> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> </target_preparer> |