diff options
45 files changed, 815 insertions, 453 deletions
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 3da696ad0bc7..7fbaf1027af6 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -882,10 +882,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } static Uri readFrom(Parcel parcel) { + final StringUri stringUri = new StringUri(parcel.readString8()); return new OpaqueUri( - parcel.readString8(), - Part.readFrom(parcel), - Part.readFrom(parcel) + stringUri.parseScheme(), + stringUri.getSsp(), + stringUri.getFragmentPart() ); } @@ -895,9 +896,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString8(scheme); - ssp.writeTo(parcel); - fragment.writeTo(parcel); + parcel.writeString8(toString()); } public boolean isHierarchical() { @@ -1196,22 +1195,25 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { Part query, Part fragment) { this.scheme = scheme; this.authority = Part.nonNull(authority); - this.path = path == null ? PathPart.NULL : path; + this.path = generatePath(path); this.query = Part.nonNull(query); this.fragment = Part.nonNull(fragment); } - static Uri readFrom(Parcel parcel) { - final String scheme = parcel.readString8(); - final Part authority = Part.readFrom(parcel); + private PathPart generatePath(PathPart originalPath) { // In RFC3986 the path should be determined based on whether there is a scheme or // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). final boolean hasSchemeOrAuthority = (scheme != null && scheme.length() > 0) || !authority.isEmpty(); - final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); - final Part query = Part.readFrom(parcel); - final Part fragment = Part.readFrom(parcel); - return new HierarchicalUri(scheme, authority, path, query, fragment); + final PathPart newPath = hasSchemeOrAuthority ? PathPart.makeAbsolute(originalPath) + : originalPath; + return newPath == null ? PathPart.NULL : newPath; + } + + static Uri readFrom(Parcel parcel) { + final StringUri stringUri = new StringUri(parcel.readString8()); + return new HierarchicalUri(stringUri.getScheme(), stringUri.getAuthorityPart(), + stringUri.getPathPart(), stringUri.getQueryPart(), stringUri.getFragmentPart()); } public int describeContents() { @@ -1220,11 +1222,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString8(scheme); - authority.writeTo(parcel); - path.writeTo(parcel); - query.writeTo(parcel); - fragment.writeTo(parcel); + parcel.writeString8(toString()); } public boolean isHierarchical() { diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index afc567e5de5c..c88048c17160 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -97,6 +97,12 @@ public interface WindowManagerPolicyConstants { */ String EXTRA_START_REASON = "android.intent.extra.EXTRA_START_REASON"; + /** + * Set to {@code true} when intent was invoked from pressing one of the brightness keys. + * @hide + */ + String EXTRA_FROM_BRIGHTNESS_KEY = "android.intent.extra.FROM_BRIGHTNESS_KEY"; + // TODO: move this to a more appropriate place. interface PointerEventListener { /** diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 89632a46267e..2a4ca79d997e 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -25,8 +25,6 @@ import junit.framework.TestCase; import java.io.File; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -869,84 +867,90 @@ public class UriTest extends TestCase { return (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null); } - /** Attempting to unparcel a legacy parcel format of Uri.{,Path}Part should fail. */ - public void testUnparcelLegacyPart_fails() throws Exception { - assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$Part")); - assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$PathPart")); - } - - private static void assertUnparcelLegacyPart_fails(Class partClass) throws Exception { - Parcel parcel = Parcel.obtain(); - parcel.writeInt(0 /* BOTH */); - parcel.writeString("encoded"); - parcel.writeString("decoded"); - parcel.setDataPosition(0); - - Method readFromMethod = partClass.getDeclaredMethod("readFrom", Parcel.class); - readFromMethod.setAccessible(true); - try { - readFromMethod.invoke(null, parcel); - fail(); - } catch (InvocationTargetException expected) { - Throwable targetException = expected.getTargetException(); - // Check that the exception was thrown for the correct reason. - assertEquals("Unknown representation: 0", targetException.getMessage()); - } finally { - parcel.recycle(); - } - } - - private Uri buildUriFromRawParcel(boolean argumentsEncoded, + private Uri buildUriFromParts(boolean argumentsEncoded, String scheme, String authority, String path, String query, String fragment) { - // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). - final int representation = argumentsEncoded ? 1 : 2; - Parcel parcel = Parcel.obtain(); - try { - parcel.writeInt(3); // hierarchical - parcel.writeString8(scheme); - parcel.writeInt(representation); - parcel.writeString8(authority); - parcel.writeInt(representation); - parcel.writeString8(path); - parcel.writeInt(representation); - parcel.writeString8(query); - parcel.writeInt(representation); - parcel.writeString8(fragment); - parcel.setDataPosition(0); - return Uri.CREATOR.createFromParcel(parcel); - } finally { - parcel.recycle(); + final Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + if (argumentsEncoded) { + builder.encodedAuthority(authority); + builder.encodedPath(path); + builder.encodedQuery(query); + builder.encodedFragment(fragment); + } else { + builder.authority(authority); + builder.path(path); + builder.query(query); + builder.fragment(fragment); } + return builder.build(); } public void testUnparcelMalformedPath() { // Regression tests for b/171966843. // Test cases with arguments encoded (covering testing `scheme` * `authority` options). - Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + Uri uri0 = buildUriFromParts(true, "https", "google.com", "@evil.com", null, null); assertEquals("https://google.com/@evil.com", uri0.toString()); - Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + Uri uri1 = buildUriFromParts(true, null, "google.com", "@evil.com", "name=spark", "x"); assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); - Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + Uri uri2 = buildUriFromParts(true, "http:", null, "@evil.com", null, null); assertEquals("http::/@evil.com", uri2.toString()); - Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + Uri uri3 = buildUriFromParts(true, null, null, "@evil.com", null, null); assertEquals("@evil.com", uri3.toString()); // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). - Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + Uri uriA = buildUriFromParts(false, "https", "google.com", "@evil.com", null, null); assertEquals("https://google.com/%40evil.com", uriA.toString()); - Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + Uri uriB = buildUriFromParts(false, null, "google.com", "@evil.com", null, null); assertEquals("//google.com/%40evil.com", uriB.toString()); - Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + Uri uriC = buildUriFromParts(false, "http:", null, "@evil.com", null, null); assertEquals("http::/%40evil.com", uriC.toString()); - Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + Uri uriD = buildUriFromParts(false, null, null, "@evil.com", "name=spark", "y"); assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); } + public void testParsedUriFromStringEquality() { + Uri uri = buildUriFromParts( + true, "https", "google.com", "@evil.com", null, null); + assertEquals(uri, Uri.parse(uri.toString())); + Uri uri2 = buildUriFromParts( + true, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null); + assertEquals(uri2, Uri.parse(uri2.toString())); + Uri uri3 = buildUriFromParts( + false, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null); + assertEquals(uri3, Uri.parse(uri3.toString())); + } + + public void testParceledUrisAreEqual() { + Uri opaqueUri = Uri.fromParts("fake://uri#", "ssp", "fragment"); + Parcel parcel = Parcel.obtain(); + try { + opaqueUri.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel); + Uri parsedUri = Uri.parse(postParcelUri.toString()); + assertEquals(parsedUri.getScheme(), postParcelUri.getScheme()); + } finally { + parcel.recycle(); + } + + Uri hierarchicalUri = new Uri.Builder().scheme("fake://uri#").authority("auth").build(); + parcel = Parcel.obtain(); + try { + hierarchicalUri.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel); + Uri parsedUri = Uri.parse(postParcelUri.toString()); + assertEquals(parsedUri.getScheme(), postParcelUri.getScheme()); + } finally { + parcel.recycle(); + } + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); diff --git a/packages/SystemUI/res/color/brightness_slider_overlay_color.xml b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml new file mode 100644 index 000000000000..a8abd793bd00 --- /dev/null +++ b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" /> + <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" /> + <item android:color="@color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml index 2ea90c717863..a9e7adf668a9 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml @@ -26,6 +26,13 @@ <corners android:radius="@dimen/rounded_slider_corner_radius"/> </shape> </item> + <item> + <shape> + <corners android:radius="@dimen/rounded_slider_corner_radius" /> + <size android:height="@dimen/rounded_slider_height" /> + <solid android:color="@color/brightness_slider_overlay_color" /> + </shape> + </item> <item android:id="@+id/slider_icon" android:gravity="center_vertical|right" diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml index 87b5a4c2fc5b..32dc4b335f7e 100644 --- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml +++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml @@ -20,7 +20,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurface"/> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/> <size android:width="@dimen/keyguard_affordance_fixed_width" android:height="@dimen/keyguard_affordance_fixed_height"/> diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml index 05ff1b1c2e6f..8eb62ecba5fa 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -39,6 +39,7 @@ android:singleLine="true" android:marqueeRepeatLimit="1" android:ellipsize="marquee" + android:importantForAccessibility="no" style="@style/TextAppearance.AuthCredential.Subtitle"/> <TextView diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml index e95c6a79733c..91550b3dcac0 100644 --- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml @@ -34,5 +34,6 @@ android:paddingEnd="0dp" android:progressDrawable="@drawable/brightness_progress_drawable" android:splitTrack="false" + android:clickable="true" /> </com.android.systemui.settings.brightness.BrightnessSliderView> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt index 3197c0935d0b..fb580ca54aff 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.biometrics.domain.model +package com.android.systemui.biometrics.shared.model import android.hardware.biometrics.BiometricAuthenticator diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 64df6a03001d..e5a4d1a644f1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -46,9 +46,9 @@ import com.android.systemui.biometrics.AuthIconController import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.model.BiometricModalities -import com.android.systemui.biometrics.domain.model.BiometricModality -import com.android.systemui.biometrics.domain.model.asBiometricModality +import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.PromptKind +import com.android.systemui.biometrics.shared.model.asBiometricModality import com.android.systemui.biometrics.ui.BiometricPromptLayout import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode import com.android.systemui.biometrics.ui.viewmodel.PromptMessage @@ -396,7 +396,6 @@ private class Spaghetti( private var lifecycleScope: CoroutineScope? = null private var modalities: BiometricModalities = BiometricModalities() - private var faceFailedAtLeastOnce = false private var legacyCallback: Callback? = null override var legacyIconController: AuthIconController? = null @@ -476,19 +475,15 @@ private class Spaghetti( viewModel.ensureFingerprintHasStarted(isDelayed = true) applicationScope.launch { - val suppress = - modalities.hasFaceAndFingerprint && - (failedModality == BiometricModality.Face) && - faceFailedAtLeastOnce - if (failedModality == BiometricModality.Face) { - faceFailedAtLeastOnce = true - } - viewModel.showTemporaryError( failureReason, messageAfterError = modalities.asDefaultHelpMessage(applicationContext), authenticateAfterError = modalities.hasFingerprint, - suppressIfErrorShowing = suppress, + suppressIf = { currentMessage -> + modalities.hasFaceAndFingerprint && + failedModality == BiometricModality.Face && + currentMessage.isError + }, failedModality = failedModality, ) } @@ -501,11 +496,10 @@ private class Spaghetti( } applicationScope.launch { - val suppress = - modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face) viewModel.showTemporaryError( error, - suppressIfErrorShowing = suppress, + messageAfterError = modalities.asDefaultHelpMessage(applicationContext), + authenticateAfterError = modalities.hasFingerprint, ) delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong()) legacyCallback?.onAction(Callback.ACTION_ERROR) @@ -522,6 +516,7 @@ private class Spaghetti( viewModel.showTemporaryError( help, messageAfterError = modalities.asDefaultHelpMessage(applicationContext), + authenticateAfterError = modalities.hasFingerprint, hapticFeedback = false, ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt index 444082ca2742..2f9557f70a32 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt @@ -16,7 +16,7 @@ package com.android.systemui.biometrics.ui.viewmodel -import com.android.systemui.biometrics.domain.model.BiometricModality +import com.android.systemui.biometrics.shared.model.BiometricModality /** * The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt index 219da716f7d9..50f491142949 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt @@ -33,9 +33,9 @@ sealed interface PromptMessage { else -> "" } - /** If this is an [Error] or [Help] message. */ - val isErrorOrHelp: Boolean - get() = this is Error || this is Help + /** If this is an [Error]. */ + val isError: Boolean + get() = this is Error /** An error message. */ data class Error(val errorMessage: String) : PromptMessage diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index d63bf57013e5..8a2e4059ee73 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -20,7 +20,7 @@ import android.util.Log import com.android.systemui.biometrics.AuthBiometricView import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.model.BiometricModalities -import com.android.systemui.biometrics.domain.model.BiometricModality +import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject @@ -210,35 +210,33 @@ constructor( * Show a temporary error [message] associated with an optional [failedModality] and play * [hapticFeedback]. * - * An optional [messageAfterError] will be shown via [showAuthenticating] when - * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is - * dismissed. + * The [messageAfterError] will be shown via [showAuthenticating] when [authenticateAfterError] + * is set (or via [showHelp] when not set) after the error is dismissed. * - * The error is ignored if the user has already authenticated or if [suppressIfErrorShowing] is - * set and an error message is already showing. + * The error is ignored if the user has already authenticated or if [suppressIf] is true given + * the currently showing [PromptMessage]. */ suspend fun showTemporaryError( message: String, + messageAfterError: String, + authenticateAfterError: Boolean, + suppressIf: (PromptMessage) -> Boolean = { false }, hapticFeedback: Boolean = true, - messageAfterError: String = "", - authenticateAfterError: Boolean = false, - suppressIfErrorShowing: Boolean = false, failedModality: BiometricModality = BiometricModality.None, ) = coroutineScope { if (_isAuthenticated.value.isAuthenticated) { return@coroutineScope } - if (_message.value.isErrorOrHelp && suppressIfErrorShowing) { - if (_isAuthenticated.value.isNotAuthenticated) { - _canTryAgainNow.value = supportsRetry(failedModality) - } + + _canTryAgainNow.value = supportsRetry(failedModality) + + if (suppressIf(_message.value)) { return@coroutineScope } _isAuthenticating.value = false _isAuthenticated.value = PromptAuthState(false) _forceMediumSize.value = true - _canTryAgainNow.value = supportsRetry(failedModality) _message.value = PromptMessage.Error(message) _legacyState.value = AuthBiometricView.STATE_ERROR @@ -374,7 +372,9 @@ constructor( AuthBiometricView.STATE_AUTHENTICATED } - vibrator.success(modality) + if (!needsUserConfirmation) { + vibrator.success(modality) + } messageJob?.cancel() messageJob = null @@ -420,6 +420,8 @@ constructor( _message.value = PromptMessage.Empty _legacyState.value = AuthBiometricView.STATE_AUTHENTICATED + vibrator.success(authState.authenticatedModality) + messageJob?.cancel() messageJob = null } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 2513c81c17f8..62a484d42dec 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -25,6 +25,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -38,9 +40,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -53,6 +52,7 @@ constructor( private val repository: BouncerRepository, private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, + featureFlags: FeatureFlags, @Assisted private val containerName: String, ) { @@ -95,36 +95,14 @@ constructor( val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible init { - // UNLOCKING SHOWS Gone. - // - // Move to the gone scene if the device becomes unlocked while on the bouncer scene. - applicationScope.launch { - sceneInteractor - .currentScene(containerName) - .flatMapLatest { currentScene -> - if (currentScene.key == SceneKey.Bouncer) { - authenticationInteractor.isUnlocked - } else { - flowOf(false) - } - } - .distinctUntilChanged() - .collect { isUnlocked -> - if (isUnlocked) { - sceneInteractor.setCurrentScene( - containerName = containerName, - scene = SceneModel(SceneKey.Gone), - ) + if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { + // Clear the message if moved from throttling to no-longer throttling. + applicationScope.launch { + isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) -> + if (wasThrottled && !currentlyThrottled) { + clearMessage() } } - } - - // Clear the message if moved from throttling to no-longer throttling. - applicationScope.launch { - isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) -> - if (wasThrottled && !currentlyThrottled) { - clearMessage() - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 5425022e11b7..a4ef5cec6525 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -23,6 +23,8 @@ import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.util.kotlin.pairwise import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -49,6 +51,7 @@ constructor( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, interactorFactory: BouncerInteractor.Factory, + featureFlags: FeatureFlags, @Assisted containerName: String, ) { private val interactor: BouncerInteractor = interactorFactory.create(containerName) @@ -102,15 +105,48 @@ constructor( ) init { - applicationScope.launch { - _authMethod.subscriptionCount - .pairwise() - .map { (previousCount, currentCount) -> currentCount > previousCount } - .collect { subscriberAdded -> - if (subscriberAdded) { - reloadAuthMethod() + if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { + applicationScope.launch { + interactor.isThrottled + .map { isThrottled -> + if (isThrottled) { + when (interactor.getAuthenticationMethod()) { + is AuthenticationMethodModel.Pin -> + R.string.kg_too_many_failed_pin_attempts_dialog_message + is AuthenticationMethodModel.Password -> + R.string.kg_too_many_failed_password_attempts_dialog_message + is AuthenticationMethodModel.Pattern -> + R.string.kg_too_many_failed_pattern_attempts_dialog_message + else -> null + }?.let { stringResourceId -> + applicationContext.getString( + stringResourceId, + interactor.throttling.value.failedAttemptCount, + ceil(interactor.throttling.value.remainingMs / 1000f).toInt(), + ) + } + } else { + null + } + } + .distinctUntilChanged() + .collect { dialogMessageOrNull -> + if (dialogMessageOrNull != null) { + _throttlingDialogMessage.value = dialogMessageOrNull + } + } + } + + applicationScope.launch { + _authMethod.subscriptionCount + .pairwise() + .map { (previousCount, currentCount) -> currentCount > previousCount } + .collect { subscriberAdded -> + if (subscriberAdded) { + reloadAuthMethod() + } } - } + } } } @@ -144,39 +180,6 @@ constructor( */ val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow() - init { - applicationScope.launch { - interactor.isThrottled - .map { isThrottled -> - if (isThrottled) { - when (interactor.getAuthenticationMethod()) { - is AuthenticationMethodModel.Pin -> - R.string.kg_too_many_failed_pin_attempts_dialog_message - is AuthenticationMethodModel.Password -> - R.string.kg_too_many_failed_password_attempts_dialog_message - is AuthenticationMethodModel.Pattern -> - R.string.kg_too_many_failed_pattern_attempts_dialog_message - else -> null - }?.let { stringResourceId -> - applicationContext.getString( - stringResourceId, - interactor.throttling.value.failedAttemptCount, - ceil(interactor.throttling.value.remainingMs / 1000f).toInt(), - ) - } - } else { - null - } - } - .distinctUntilChanged() - .collect { dialogMessageOrNull -> - if (dialogMessageOrNull != null) { - _throttlingDialogMessage.value = dialogMessageOrNull - } - } - } - } - /** Notifies that the emergency services button was clicked. */ fun onEmergencyServicesButtonClicked() { // TODO(b/280877228): implement this diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 69c381b4a58e..a5a799565eaf 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -91,6 +91,11 @@ object Flags { val NOTIFICATION_SHELF_REFACTOR = unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true) + // TODO(b/290787599): Tracking Bug + @JvmField + val NOTIFICATION_ICON_CONTAINER_REFACTOR = + unreleasedFlag(278765923, "notification_icon_container_refactor") + // TODO(b/288326013): Tracking Bug @JvmField val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = @@ -258,6 +263,16 @@ object Flags { @JvmField val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true) + /** + * Migrate the bottom area to the new keyguard root view. + * Because there is no such thing as a "bottom area" after this, this also breaks it up into + * many smaller, modular pieces. + */ + // TODO(b/290652751): Tracking bug. + @JvmField + val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA = + unreleasedFlag(290652751, "migrate_split_keyguard_bottom_area") + /** Whether to listen for fingerprint authentication over keyguard occluding activities. */ // TODO(b/283260512): Tracking bug. @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt index c8f7efbeb397..1c200b086990 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt @@ -20,20 +20,14 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch /** Hosts business and application state accessing logic for the lockscreen scene. */ class LockscreenSceneInteractor @@ -42,7 +36,6 @@ constructor( @Application applicationScope: CoroutineScope, private val authenticationInteractor: AuthenticationInteractor, bouncerInteractorFactory: BouncerInteractor.Factory, - private val sceneInteractor: SceneInteractor, @Assisted private val containerName: String, ) { private val bouncerInteractor: BouncerInteractor = @@ -72,46 +65,6 @@ constructor( initialValue = false, ) - init { - // LOCKING SHOWS Lockscreen. - // - // Move to the lockscreen scene if the device becomes locked while in any scene. - applicationScope.launch { - authenticationInteractor.isUnlocked - .map { !it } - .distinctUntilChanged() - .collect { isLocked -> - if (isLocked) { - sceneInteractor.setCurrentScene( - containerName = containerName, - scene = SceneModel(SceneKey.Lockscreen), - ) - } - } - } - - // BYPASS UNLOCK. - // - // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the - // lockscreen scene. - applicationScope.launch { - combine( - authenticationInteractor.isBypassEnabled, - authenticationInteractor.isUnlocked, - sceneInteractor.currentScene(containerName), - ::Triple, - ) - .collect { (isBypassEnabled, isUnlocked, currentScene) -> - if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) { - sceneInteractor.setCurrentScene( - containerName = containerName, - scene = SceneModel(SceneKey.Gone), - ) - } - } - } - } - /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */ fun dismissLockscreen() { bouncerInteractor.showOrUnlockDevice(containerName = containerName) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 7d14198bdb17..db84268f7c58 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -343,9 +343,9 @@ object KeyguardBottomAreaViewBinder { Utils.getColorAttrDefaultColor( view.context, if (viewModel.isActivated) { - com.android.internal.R.attr.textColorPrimaryInverse + com.android.internal.R.attr.materialColorOnPrimaryFixed } else { - com.android.internal.R.attr.textColorPrimary + com.android.internal.R.attr.materialColorOnSurface }, ) ) @@ -355,9 +355,9 @@ object KeyguardBottomAreaViewBinder { Utils.getColorAttr( view.context, if (viewModel.isActivated) { - com.android.internal.R.attr.colorAccentPrimary + com.android.internal.R.attr.materialColorPrimaryFixed } else { - com.android.internal.R.attr.colorSurface + com.android.internal.R.attr.materialColorSurfaceContainerHigh } ) } else { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt index 91400196bbb1..59f82f034723 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt @@ -17,6 +17,7 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags @@ -24,9 +25,11 @@ import com.android.systemui.flags.Flags import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -41,17 +44,19 @@ class SystemUiDefaultSceneContainerStartable constructor( @Application private val applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, + private val authenticationInteractor: AuthenticationInteractor, private val featureFlags: FeatureFlags, ) : CoreStartable { override fun start() { if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { - keepVisibilityUpdated() + hydrateVisibility() + automaticallySwitchScenes() } } - /** Drives visibility of the scene container. */ - private fun keepVisibilityUpdated() { + /** Updates the visibility of the scene container based on the current scene. */ + private fun hydrateVisibility() { applicationScope.launch { sceneInteractor .currentScene(CONTAINER_NAME) @@ -63,6 +68,48 @@ constructor( } } + /** Switches between scenes based on ever-changing application state. */ + private fun automaticallySwitchScenes() { + applicationScope.launch { + authenticationInteractor.isUnlocked + .map { isUnlocked -> + val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key + val isBypassEnabled = authenticationInteractor.isBypassEnabled.value + when { + isUnlocked -> + when (currentSceneKey) { + // When the device becomes unlocked in Bouncer, go to the Gone. + is SceneKey.Bouncer -> SceneKey.Gone + // When the device becomes unlocked in Lockscreen, go to Gone if + // bypass is enabled. + is SceneKey.Lockscreen -> SceneKey.Gone.takeIf { isBypassEnabled } + // We got unlocked while on a scene that's not Lockscreen or + // Bouncer, no need to change scenes. + else -> null + } + // When the device becomes locked, to Lockscreen. + !isUnlocked -> + when (currentSceneKey) { + // Already on lockscreen or bouncer, no need to change scenes. + is SceneKey.Lockscreen, + is SceneKey.Bouncer -> null + // We got locked while on a scene that's not Lockscreen or Bouncer, + // go to Lockscreen. + else -> SceneKey.Lockscreen + } + else -> null + } + } + .filterNotNull() + .collect { targetSceneKey -> + sceneInteractor.setCurrentScene( + containerName = CONTAINER_NAME, + scene = SceneModel(targetSceneKey), + ) + } + } + } + companion object { private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 5199bd43f982..182e4569b549 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -18,6 +18,7 @@ package com.android.systemui.settings.brightness; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY; import android.app.Activity; import android.graphics.Rect; @@ -29,8 +30,10 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; @@ -38,34 +41,42 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; -import java.util.concurrent.Executor; import javax.inject.Inject; /** A dialog that provides controls for adjusting the screen brightness. */ public class BrightnessDialog extends Activity { + @VisibleForTesting + static final int DIALOG_TIMEOUT_MILLIS = 3000; + private BrightnessController mBrightnessController; private final BrightnessSliderController.Factory mToggleSliderFactory; private final UserTracker mUserTracker; private final DisplayTracker mDisplayTracker; - private final Executor mMainExecutor; + private final DelayableExecutor mMainExecutor; private final Handler mBackgroundHandler; + private final AccessibilityManagerWrapper mAccessibilityMgr; + private Runnable mCancelTimeoutRunnable; @Inject public BrightnessDialog( UserTracker userTracker, DisplayTracker displayTracker, BrightnessSliderController.Factory factory, - @Main Executor mainExecutor, - @Background Handler bgHandler) { + @Main DelayableExecutor mainExecutor, + @Background Handler bgHandler, + AccessibilityManagerWrapper accessibilityMgr) { mUserTracker = userTracker; mDisplayTracker = displayTracker; mToggleSliderFactory = factory; mMainExecutor = mainExecutor; mBackgroundHandler = bgHandler; + mAccessibilityMgr = accessibilityMgr; } @@ -122,6 +133,14 @@ public class BrightnessDialog extends Activity { } @Override + protected void onResume() { + super.onResume(); + if (triggeredByBrightnessKey()) { + scheduleTimeout(); + } + } + + @Override protected void onPause() { super.onPause(); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); @@ -139,9 +158,25 @@ public class BrightnessDialog extends Activity { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { + if (mCancelTimeoutRunnable != null) { + mCancelTimeoutRunnable.run(); + } finish(); } return super.onKeyDown(keyCode, event); } + + private boolean triggeredByBrightnessKey() { + return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false); + } + + private void scheduleTimeout() { + if (mCancelTimeoutRunnable != null) { + mCancelTimeoutRunnable.run(); + } + final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, + AccessibilityManager.FLAG_CONTENT_CONTROLS); + mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index cff71d2edd23..fbbee53468c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -160,7 +160,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi private ObjectAnimator mIconAppearAnimator; private ObjectAnimator mDotAnimator; private float mDotAppearAmount; - private OnVisibilityChangedListener mOnVisibilityChangedListener; private int mDrawableColor; private int mIconColor; private int mDecorColor; @@ -179,7 +178,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi private int mCachedContrastBackgroundColor = NO_COLOR; private float[] mMatrix; private ColorMatrixColorFilter mMatrixColorFilter; - private boolean mIsInShelf; private Runnable mLayoutRunnable; private boolean mDismissed; private Runnable mOnDismissListener; @@ -357,19 +355,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi return mNotification != null; } - private static boolean streq(String a, String b) { - if (a == b) { - return true; - } - if (a == null && b != null) { - return false; - } - if (a != null && b == null) { - return false; - } - return a.equals(b); - } - public boolean equalIcons(Icon a, Icon b) { if (a == b) return true; if (a.getType() != b.getType()) return false; @@ -908,7 +893,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi if (targetAmount != currentAmount) { mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT, currentAmount, targetAmount); - mDotAnimator.setInterpolator(interpolator);; + mDotAnimator.setInterpolator(interpolator); mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST : duration); final boolean runRunnable = !runnableAdded; @@ -965,22 +950,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } } - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - if (mOnVisibilityChangedListener != null) { - mOnVisibilityChangedListener.onVisibilityChanged(visibility); - } - } - public float getDotAppearAmount() { return mDotAppearAmount; } - public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) { - mOnVisibilityChangedListener = listener; - } - public void setDozing(boolean dozing, boolean fade, long delay) { mDozer.setDozing(f -> { mDozeAmount = f; @@ -1014,14 +987,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi outRect.bottom += translationY; } - public void setIsInShelf(boolean isInShelf) { - mIsInShelf = isInShelf; - } - - public boolean isInShelf() { - return mIsInShelf; - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -1103,8 +1068,4 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi public boolean showsConversation() { return mShowsConversation; } - - public interface OnVisibilityChangedListener { - void onVisibilityChanged(int newVisibility); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 0fde3ab3d19c..e18c9d86d74b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -33,14 +33,11 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider; -import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.wm.shell.bubbles.Bubbles; @@ -91,8 +88,6 @@ public class NotificationIconAreaController implements private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private final Context mContext; - private final DemoModeController mDemoModeController; - private final FeatureFlags mFeatureFlags; private int mAodIconAppearTranslation; @@ -139,8 +134,7 @@ public class NotificationIconAreaController implements wakeUpCoordinator.addListener(this); mBypassController = keyguardBypassController; mBubblesOptional = bubblesOptional; - mDemoModeController = demoModeController; - mDemoModeController.addCallback(this); + demoModeController.addCallback(this); mStatusBarWindowController = statusBarWindowController; mScreenOffAnimationController = screenOffAnimationController; notificationListener.addNotificationSettingsListener(mSettingsListener); @@ -163,7 +157,6 @@ public class NotificationIconAreaController implements LayoutInflater layoutInflater = LayoutInflater.from(context); mNotificationIconArea = inflateIconArea(layoutInflater); mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons); - } /** @@ -185,16 +178,6 @@ public class NotificationIconAreaController implements updateIconLayoutParams(mContext); } - /** - * Update position of the view, with optional animation - */ - public void updatePosition(int x, AnimationProperties props, boolean animate) { - if (mAodIcons != null) { - PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props, - animate); - } - } - public void setupShelf(NotificationShelfController notificationShelfController) { NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); mShelfIcons = notificationShelfController.getShelfIcons(); @@ -303,6 +286,7 @@ public class NotificationIconAreaController implements } return true; } + /** * Updates the notifications with the given list of notifications to display. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 418f9203761a..3770c1df77ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -54,19 +54,13 @@ import java.util.function.Consumer; * correctly on the screen. */ public class NotificationIconContainer extends ViewGroup { - /** - * A float value indicating how much before the overflow start the icons should transform into - * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts - * 1 icon width early. - */ - public static final float OVERFLOW_EARLY_AMOUNT = 0.2f; private static final int NO_VALUE = Integer.MIN_VALUE; private static final String TAG = "NotificationIconContainer"; private static final boolean DEBUG = false; private static final boolean DEBUG_OVERFLOW = false; private static final int CANNED_ANIMATION_DURATION = 100; private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); + private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); @Override public AnimationFilter getAnimationFilter() { @@ -75,7 +69,7 @@ public class NotificationIconContainer extends ViewGroup { }.setDuration(200); private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter() + private final AnimationFilter mAnimationFilter = new AnimationFilter() .animateX() .animateY() .animateAlpha() @@ -92,7 +86,7 @@ public class NotificationIconContainer extends ViewGroup { * Temporary AnimationProperties to avoid unnecessary allocations. */ private static final AnimationProperties sTempProperties = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter(); + private final AnimationFilter mAnimationFilter = new AnimationFilter(); @Override public AnimationFilter getAnimationFilter() { @@ -101,7 +95,7 @@ public class NotificationIconContainer extends ViewGroup { }; private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); + private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); @Override public AnimationFilter getAnimationFilter() { @@ -115,7 +109,7 @@ public class NotificationIconContainer extends ViewGroup { */ private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); + private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); @Override public AnimationFilter getAnimationFilter() { @@ -128,7 +122,7 @@ public class NotificationIconContainer extends ViewGroup { * This animates the translation back to the right position. */ private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() { - private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); + private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); @Override public AnimationFilter getAnimationFilter() { @@ -147,7 +141,6 @@ public class NotificationIconContainer extends ViewGroup { private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); private int mDotPadding; - private int mStaticDotRadius; private int mStaticDotDiameter; private int mActualLayoutWidth = NO_VALUE; private float mActualPaddingEnd = NO_VALUE; @@ -170,7 +163,7 @@ public class NotificationIconContainer extends ViewGroup { private boolean mIsShowingOverflowDot; private StatusBarIconView mIsolatedIcon; private Rect mIsolatedIconLocation; - private int[] mAbsolutePosition = new int[2]; + private final int[] mAbsolutePosition = new int[2]; private View mIsolatedIconForAnimation; private int mThemedTextColorPrimary; @@ -186,8 +179,8 @@ public class NotificationIconContainer extends ViewGroup { mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons); mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); - mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); - mStaticDotDiameter = 2 * mStaticDotRadius; + int staticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); + mStaticDotDiameter = 2 * staticDotRadius; final Context themedContext = new ContextThemeWrapper(getContext(), com.android.internal.R.style.Theme_DeviceDefault_DayNight); @@ -699,7 +692,6 @@ public class NotificationIconContainer extends ViewGroup { } public class IconState extends ViewState { - public static final int NO_VALUE = NotificationIconContainer.NO_VALUE; public float iconAppearAmount = 1.0f; public float clampedAppearAmount = 1.0f; public int visibleState; @@ -817,8 +809,6 @@ public class NotificationIconContainer extends ViewGroup { super.applyToView(view); } sTempProperties.setAnimationEndAction(null); - boolean inShelf = iconAppearAmount == 1.0f; - icon.setIsInShelf(inShelf); } justAdded = false; justReplaced = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index cb2a78d8be35..5c1dfbe42cf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -51,6 +51,7 @@ import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -306,6 +307,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Nullable private TaskbarDelegate mTaskbarDelegate; private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onTrustGrantedForCurrentUser( + boolean dismissKeyguard, + boolean newlyUnlocked, + @NonNull TrustGrantFlags flags, + @Nullable String message + ) { + updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide()); + } + @Override public void onEmergencyCallAction() { // Since we won't get a setOccluded call we have to reset the view manually such that @@ -431,7 +442,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); } - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); } /** Register a callback, to be invoked by the Predictive Back system. */ @@ -1570,14 +1580,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mode == KeyguardSecurityModel.SecurityMode.SimPuk; } - private KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide()); - } - }; - /** * Delegate used to send show and hide events to an alternate authentication method instead of * the regular pin/pattern/password bouncer. diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt index fff1b81db628..278a43ea1bf1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.biometrics.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.model.BiometricModality +import com.android.systemui.biometrics.shared.model.BiometricModality import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 87c9e583af4d..91140a9b0fc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -27,11 +27,12 @@ import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl import com.android.systemui.biometrics.domain.model.BiometricModalities -import com.android.systemui.biometrics.domain.model.BiometricModality import com.android.systemui.biometrics.extractAuthenticatorTypes import com.android.systemui.biometrics.faceSensorPropertiesInternal import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal +import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat @@ -131,20 +132,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun plays_haptic_on_authenticated() = runGenericTest { - viewModel.showAuthenticated(testCase.authenticatedModality, 1000L) + fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() = + runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) - verify(vibrator).vibrateAuthSuccess(any()) - verify(vibrator, never()).vibrateAuthError(any()) - } + viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) - @Test - fun plays_no_haptic_on_confirm() = runGenericTest { - viewModel.confirmAuthenticated() + verify(vibrator, if (expectConfirmation) never() else times(1)) + .vibrateAuthSuccess(any()) - verify(vibrator, never()).vibrateAuthSuccess(any()) - verify(vibrator, never()).vibrateAuthError(any()) - } + if (expectConfirmation) { + viewModel.confirmAuthenticated() + } + + verify(vibrator).vibrateAuthSuccess(any()) + verify(vibrator, never()).vibrateAuthError(any()) + } private suspend fun TestScope.showAuthenticated( authenticatedModality: BiometricModality, @@ -204,7 +207,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Test fun plays_haptic_on_errors() = runGenericTest { - viewModel.showTemporaryError("so sad", hapticFeedback = true) + viewModel.showTemporaryError( + "so sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = true, + ) verify(vibrator).vibrateAuthError(any()) verify(vibrator, never()).vibrateAuthSuccess(any()) @@ -212,7 +220,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Test fun plays_haptic_on_errors_unless_skipped() = runGenericTest { - viewModel.showTemporaryError("still sad", hapticFeedback = false) + viewModel.showTemporaryError( + "still sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = false, + ) verify(vibrator, never()).vibrateAuthError(any()) verify(vibrator, never()).vibrateAuthSuccess(any()) @@ -287,7 +300,13 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(canTryAgain).isFalse() } - val errorJob = launch { viewModel.showTemporaryError("error") } + val errorJob = launch { + viewModel.showTemporaryError( + "error", + messageAfterError = "", + authenticateAfterError = false, + ) + } verifyNoError() errorJob.join() verifyNoError() @@ -306,12 +325,66 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(messageIsShowing).isTrue() } - // @Test - fun `suppress errors`() = runGenericTest { - val errorMessage = "woot" - val message by collectLastValue(viewModel.message) + @Test + fun suppress_temporary_error() = runGenericTest { + val messages by collectValues(viewModel.message) + + for (error in listOf("never", "see", "me")) { + launch { + viewModel.showTemporaryError( + error, + messageAfterError = "or me", + authenticateAfterError = false, + suppressIf = { _ -> true }, + ) + } + } + + testScheduler.advanceUntilIdle() + assertThat(messages).containsExactly(PromptMessage.Empty) + } - val errorJob = launch { viewModel.showTemporaryError(errorMessage) } + @Test + fun suppress_temporary_error_when_already_showing_when_requested() = + suppress_temporary_error_when_already_showing(suppress = true) + + @Test + fun do_not_suppress_temporary_error_when_already_showing_when_not_requested() = + suppress_temporary_error_when_already_showing(suppress = false) + + private fun suppress_temporary_error_when_already_showing(suppress: Boolean) = runGenericTest { + val errors = listOf("woot", "oh yeah", "nope") + val afterSuffix = "(after)" + val expectedErrorMessage = if (suppress) errors.first() else errors.last() + val messages by collectValues(viewModel.message) + + for (error in errors) { + launch { + viewModel.showTemporaryError( + error, + messageAfterError = "$error $afterSuffix", + authenticateAfterError = false, + suppressIf = { currentMessage -> suppress && currentMessage.isError }, + ) + } + } + + testScheduler.runCurrent() + assertThat(messages) + .containsExactly( + PromptMessage.Empty, + PromptMessage.Error(expectedErrorMessage), + ) + .inOrder() + + testScheduler.advanceUntilIdle() + assertThat(messages) + .containsExactly( + PromptMessage.Empty, + PromptMessage.Error(expectedErrorMessage), + PromptMessage.Help("$expectedErrorMessage $afterSuffix"), + ) + .inOrder() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index c2219a4d82eb..481f36e8ea38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -345,23 +345,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } - @Test - fun switchesToGone_whenUnlocked() = - testScope.runTest { - utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - - utils.authenticationRepository.setUnlocked(true) - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - private fun assertTryAgainMessage( message: String?, time: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 0356036fa78f..0df0a17931f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -52,7 +52,10 @@ class BouncerViewModelTest : SysuiTestCase() { authenticationInteractor = authenticationInteractor, sceneInteractor = utils.sceneInteractor(), ) - private val underTest = utils.bouncerViewModel(bouncerInteractor) + private val underTest = + utils.bouncerViewModel( + bouncerInteractor = bouncerInteractor, + ) @Test fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 7d6c4a1a1455..5c6d4c69b50b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -20,7 +20,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -54,16 +53,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, ) private val bouncerViewModel = - BouncerViewModel( - applicationContext = context, - applicationScope = testScope.backgroundScope, - interactorFactory = - object : BouncerInteractor.Factory { - override fun create(containerName: String): BouncerInteractor { - return bouncerInteractor - } - }, - containerName = SceneTestUtils.CONTAINER_1, + utils.bouncerViewModel( + bouncerInteractor = bouncerInteractor, ) private val underTest = PinBouncerViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt index 8b36284b8620..ca6a5b6234b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt @@ -47,7 +47,6 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { private val underTest = utils.lockScreenSceneInteractor( authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, bouncerInteractor = utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, @@ -129,22 +128,6 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { } @Test - fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - runCurrent() - sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone)) - runCurrent() - utils.authenticationRepository.setUnlocked(true) - runCurrent() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - - utils.authenticationRepository.setUnlocked(false) - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - } - - @Test fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() = testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 161dd660671e..ba8e0f277b6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -56,7 +56,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { override fun create(containerName: String): LockscreenSceneInteractor { return utils.lockScreenSceneInteractor( authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, bouncerInteractor = utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 4eedc99839c6..ed7a59ea7032 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -53,7 +53,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { override fun create(containerName: String): LockscreenSceneInteractor { return utils.lockScreenSceneInteractor( authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, bouncerInteractor = utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt index df3701edc6de..df5e7bcd16be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalCoroutinesApi::class) - package com.android.systemui.scene.domain.startable import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneContainerNames @@ -29,11 +26,14 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @@ -41,29 +41,47 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() - private val featureFlags = FakeFeatureFlags() + private val featureFlags = utils.featureFlags + private val authenticationRepository = utils.authenticationRepository() + private val authenticationInteractor = + utils.authenticationInteractor( + repository = authenticationRepository, + ) private val underTest = SystemUiDefaultSceneContainerStartable( applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, + authenticationInteractor = authenticationInteractor, featureFlags = featureFlags, ) + @Before + fun setUp() { + prepareState() + } + @Test - fun start_featureEnabled_keepsVisibilityUpdated() = + fun hydrateVisibility_featureEnabled() = testScope.runTest { - featureFlags.set(Flags.SCENE_CONTAINER, true) + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) val isVisible by collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT)) + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() - sceneInteractor.setCurrentScene( - SceneContainerNames.SYSTEM_UI_DEFAULT, - SceneModel(SceneKey.Gone) - ) assertThat(isVisible).isFalse() sceneInteractor.setCurrentScene( @@ -74,14 +92,26 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { } @Test - fun start_featureDisabled_doesNotUpdateVisibility() = + fun hydrateVisibility_featureDisabled() = testScope.runTest { - featureFlags.set(Flags.SCENE_CONTAINER, false) + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) val isVisible by collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT)) + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Lockscreen, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) assertThat(isVisible).isTrue() underTest.start() + assertThat(isVisible).isTrue() sceneInteractor.setCurrentScene( SceneContainerNames.SYSTEM_UI_DEFAULT, @@ -95,4 +125,172 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { ) assertThat(isVisible).isTrue() } + + @Test + fun switchToLockscreenWhenDeviceLocks_featureEnabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + underTest.start() + + authenticationRepository.setUnlocked(false) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun switchToLockscreenWhenDeviceLocks_featureDisabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = false, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + underTest.start() + + authenticationRepository.setUnlocked(false) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + + @Test + fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = false, + initialSceneKey = SceneKey.Bouncer, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + underTest.start() + + authenticationRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + + @Test + fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = false, + initialSceneKey = SceneKey.Bouncer, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + underTest.start() + + authenticationRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + } + + @Test + fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = true, + isBypassEnabled = true, + initialSceneKey = SceneKey.Lockscreen, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + authenticationRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + + @Test + fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = true, + isBypassEnabled = false, + initialSceneKey = SceneKey.Lockscreen, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + authenticationRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = false, + isBypassEnabled = true, + initialSceneKey = SceneKey.Lockscreen, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + authenticationRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + private fun prepareState( + isFeatureEnabled: Boolean = true, + isDeviceUnlocked: Boolean = false, + isBypassEnabled: Boolean = false, + initialSceneKey: SceneKey? = null, + ) { + featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) + authenticationRepository.setUnlocked(isDeviceUnlocked) + authenticationRepository.setBypassEnabled(isBypassEnabled) + initialSceneKey?.let { + sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it)) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 16751c937f9e..5c35913f6e20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -16,12 +16,14 @@ package com.android.systemui.settings.brightness +import android.content.Intent import android.graphics.Rect import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup +import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule import com.android.systemui.R @@ -29,15 +31,20 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat -import java.util.concurrent.Executor import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @@ -48,9 +55,12 @@ class BrightnessDialogTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory - @Mock private lateinit var mainExecutor: Executor @Mock private lateinit var backgroundHandler: Handler @Mock private lateinit var brightnessSliderController: BrightnessSliderController + @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper + + private val clock = FakeSystemClock() + private val mainExecutor = FakeExecutor(clock) private var displayTracker = FakeDisplayTracker(mContext) @@ -64,7 +74,8 @@ class BrightnessDialogTest : SysuiTestCase() { displayTracker, brightnessSliderControllerFactory, mainExecutor, - backgroundHandler + backgroundHandler, + accessibilityMgr ) }, /* initialTouchMode= */ false, @@ -77,8 +88,6 @@ class BrightnessDialogTest : SysuiTestCase() { `when`(brightnessSliderControllerFactory.create(any(), any())) .thenReturn(brightnessSliderController) `when`(brightnessSliderController.rootView).thenReturn(View(context)) - - activityRule.launchActivity(null) } @After @@ -88,6 +97,7 @@ class BrightnessDialogTest : SysuiTestCase() { @Test fun testGestureExclusion() { + activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)) val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container) val lp = frame.layoutParams as ViewGroup.MarginLayoutParams @@ -104,18 +114,83 @@ class BrightnessDialogTest : SysuiTestCase() { .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height)) } + @Test + fun testTimeout() { + `when`( + accessibilityMgr.getRecommendedTimeoutMillis( + eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS), + anyInt() + ) + ) + .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS) + val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG) + intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true) + activityRule.launchActivity(intent) + + assertThat(activityRule.activity.isFinishing()).isFalse() + + clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong()) + assertThat(activityRule.activity.isFinishing()).isTrue() + } + + @Test + fun testRestartTimeout() { + `when`( + accessibilityMgr.getRecommendedTimeoutMillis( + eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS), + anyInt() + ) + ) + .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS) + val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG) + intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true) + activityRule.launchActivity(intent) + + assertThat(activityRule.activity.isFinishing()).isFalse() + + clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2) + // Restart the timeout + activityRule.activity.onResume() + + clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2) + // The dialog should not have disappeared yet + assertThat(activityRule.activity.isFinishing()).isFalse() + + clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2) + assertThat(activityRule.activity.isFinishing()).isTrue() + } + + @Test + fun testNoTimeoutIfNotStartedByBrightnessKey() { + `when`( + accessibilityMgr.getRecommendedTimeoutMillis( + eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS), + anyInt() + ) + ) + .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS) + activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)) + + assertThat(activityRule.activity.isFinishing()).isFalse() + + clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong()) + assertThat(activityRule.activity.isFinishing()).isFalse() + } + class TestDialog( userTracker: UserTracker, displayTracker: FakeDisplayTracker, brightnessSliderControllerFactory: BrightnessSliderController.Factory, - mainExecutor: Executor, - backgroundHandler: Handler + mainExecutor: DelayableExecutor, + backgroundHandler: Handler, + accessibilityMgr: AccessibilityManagerWrapper ) : BrightnessDialog( userTracker, displayTracker, brightnessSliderControllerFactory, mainExecutor, - backgroundHandler + backgroundHandler, + accessibilityMgr ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 309ab058fb64..6e9fba64263b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -22,7 +22,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1 import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat @@ -54,7 +53,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { override fun create(containerName: String): LockscreenSceneInteractor { return utils.lockScreenSceneInteractor( authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, bouncerInteractor = utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, @@ -89,7 +87,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -102,7 +101,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by + collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index c7143debf8a8..ed9cf3f2f158 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.service.trust.TrustAgentService; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -57,6 +58,8 @@ import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; @@ -84,7 +87,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.google.common.truth.Truth; @@ -154,7 +156,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Captor private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor; @Captor - private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; + private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback; @Before @@ -936,18 +938,24 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void onDeviceUnlocked_hideAlternateBouncerAndClearMessageArea() { + public void onTrustChanged_hideAlternateBouncerAndClearMessageArea() { + // GIVEN keyguard update monitor callback is registered + verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture()); + reset(mKeyguardUpdateMonitor); reset(mKeyguardMessageAreaController); - // GIVEN keyguard state controller callback is registered - verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture()); - // GIVEN alternate bouncer state = not visible when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - // WHEN the device is unlocked - mKeyguardStateControllerCallback.getValue().onUnlockedChanged(); + // WHEN the device is trusted by active unlock + mKeyguardUpdateMonitorCallback.getValue().onTrustGrantedForCurrentUser( + true, + true, + new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD + | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE), + null + ); // THEN the false visibility state is propagated to the keyguardUpdateMonitor verify(mKeyguardUpdateMonitor).setAlternateBouncerShowing(eq(false)); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index fec1187d8d11..d797962bae8b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -24,6 +24,8 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -51,6 +53,7 @@ class SceneTestUtils( ) { val testDispatcher = StandardTestDispatcher() val testScope = TestScope(testDispatcher) + val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) } private val userRepository: UserRepository by lazy { FakeUserRepository().apply { val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0)) @@ -129,6 +132,7 @@ class SceneTestUtils( repository = BouncerRepository(), authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, + featureFlags = featureFlags, containerName = CONTAINER_1, ) } @@ -145,13 +149,13 @@ class SceneTestUtils( return bouncerInteractor } }, + featureFlags = featureFlags, containerName = CONTAINER_1, ) } fun lockScreenSceneInteractor( authenticationInteractor: AuthenticationInteractor, - sceneInteractor: SceneInteractor, bouncerInteractor: BouncerInteractor, ): LockscreenSceneInteractor { return LockscreenSceneInteractor( @@ -163,7 +167,6 @@ class SceneTestUtils( return bouncerInteractor } }, - sceneInteractor = sceneInteractor, containerName = CONTAINER_1, ) } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 82af38200166..7557071d0d4b 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -20,6 +20,8 @@ import static com.android.server.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.assist.AssistStructure; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.WindowNode; @@ -40,6 +42,7 @@ import android.view.View; import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; @@ -50,6 +53,8 @@ import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + public final class Helper { @@ -83,6 +88,44 @@ public final class Helper { throw new UnsupportedOperationException("contains static members only"); } + private static boolean checkRemoteViewUriPermissions( + @UserIdInt int userId, @NonNull RemoteViews rView) { + final AtomicBoolean permissionsOk = new AtomicBoolean(true); + + rView.visitUris(uri -> { + int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri); + boolean allowed = uriOwnerId == userId; + permissionsOk.set(allowed && permissionsOk.get()); + }); + + return permissionsOk.get(); + } + + /** + * Checks the URI permissions of the remote view, + * to see if the current userId is able to access it. + * + * Returns the RemoteView that is passed if user is able, null otherwise. + * + * TODO: instead of returning a null remoteview when + * the current userId cannot access an URI, + * return a new RemoteView with the URI removed. + */ + public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) { + if (rView == null) return null; + + int userId = ActivityManager.getCurrentUser(); + + boolean ok = checkRemoteViewUriPermissions(userId, rView); + if (!ok) { + Slog.w(TAG, + "sanitizeRemoteView() user: " + userId + + " tried accessing resource that does not belong to them"); + } + return (ok ? rView : null); + } + + @Nullable static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) { if (set == null) return null; diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index dbeb624bd202..fa414e3b172b 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -53,6 +53,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.server.autofill.AutofillManagerService; +import com.android.server.autofill.Helper; import java.io.PrintWriter; import java.util.ArrayList; @@ -208,7 +209,8 @@ final class DialogFillUi { } private void setHeader(View decor, FillResponse response) { - final RemoteViews presentation = response.getDialogHeader(); + final RemoteViews presentation = + Helper.sanitizeRemoteView(response.getDialogHeader()); if (presentation == null) { return; } @@ -243,9 +245,10 @@ final class DialogFillUi { } private void initialAuthenticationLayout(View decor, FillResponse response) { - RemoteViews presentation = response.getDialogPresentation(); + RemoteViews presentation = Helper.sanitizeRemoteView( + response.getDialogPresentation()); if (presentation == null) { - presentation = response.getPresentation(); + presentation = Helper.sanitizeRemoteView(response.getPresentation()); } if (presentation == null) { throw new RuntimeException("No presentation for fill dialog authentication"); @@ -289,7 +292,8 @@ final class DialogFillUi { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - RemoteViews presentation = dataset.getFieldDialogPresentation(index); + RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldDialogPresentation(index)); if (presentation == null) { if (sDebug) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 129ce72e037d..cdfe7bb4f4a7 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -148,8 +148,9 @@ final class FillUi { final LayoutInflater inflater = LayoutInflater.from(mContext); - final RemoteViews headerPresentation = response.getHeader(); - final RemoteViews footerPresentation = response.getFooter(); + final RemoteViews headerPresentation = Helper.sanitizeRemoteView(response.getHeader()); + final RemoteViews footerPresentation = Helper.sanitizeRemoteView(response.getFooter()); + final ViewGroup decor; if (mFullScreen) { decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null); @@ -227,6 +228,9 @@ final class FillUi { ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker); final View content; try { + if (Helper.sanitizeRemoteView(response.getPresentation()) == null) { + throw new RuntimeException("Permission error accessing RemoteView"); + } content = response.getPresentation().applyWithTheme( mContext, decor, interceptionHandler, mThemeId); container.addView(content); @@ -306,7 +310,8 @@ final class FillUi { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - final RemoteViews presentation = dataset.getFieldPresentation(index); + final RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldPresentation(index)); if (presentation == null) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " + "service didn't provide a presentation for it on " + dataset); diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index f035d0764279..70382f1d5274 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -384,8 +384,7 @@ final class SaveUi { return false; } writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION); - - final RemoteViews template = customDescription.getPresentation(); + final RemoteViews template = Helper.sanitizeRemoteView(customDescription.getPresentation()); if (template == null) { Slog.w(TAG, "No remote view on custom description"); return false; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f1c0e130b2be..3325ddd58330 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7008,7 +7008,7 @@ public class NotificationManagerService extends SystemService { */ private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) { return notification.isMediaNotification() || isEnterpriseExempted(ai) - || isCallNotification(ai.packageName, ai.uid, notification) + || notification.isStyle(Notification.CallStyle.class) || isDefaultSearchSelectorPackage(ai.packageName); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 279a48084252..5cfbcaafe82e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3211,8 +3211,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { minLinearBrightness, maxLinearBrightness); mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness); - startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), - UserHandle.CURRENT_OR_SELF); + Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); + intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); } return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index a1199d99f1c3..6747cea80115 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -91,8 +91,6 @@ public final class CredentialManagerService CredentialManagerService, CredentialManagerServiceImpl> { private static final String TAG = "CredManSysService"; - private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API = - "enable_credential_description_api"; private static final String PERMISSION_DENIED_ERROR = "permission_denied"; private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR = "Caller is missing WRITE_SECURE_SETTINGS permission"; @@ -311,14 +309,7 @@ public final class CredentialManagerService } public static boolean isCredentialDescriptionApiEnabled() { - final long origId = Binder.clearCallingIdentity(); - try { - return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, - false); - } finally { - Binder.restoreCallingIdentity(origId); - } + return true; } @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked 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 30180e8f43a7..7e81ef22eb65 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -11269,7 +11269,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Given: a call notification has the flag FLAG_ONGOING_EVENT set // feature flag: ALLOW_DISMISS_ONGOING is on mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true); - when(mTelecomManager.isInManagedCall()).thenReturn(true); Person person = new Person.Builder() .setName("caller") |