From de154915432d6ed5fd37f5d2fdf739e8dc0b9975 Mon Sep 17 00:00:00 2001 From: Hawkwood Glazier Date: Tue, 31 Jan 2023 22:55:18 +0000 Subject: [PATCH 01/74] Initialize Colors when regionSampling is disabled Fixes: 266726881 Test: Manually tested color of default clock Change-Id: Id92356c955fa0c67764d4b5248ffb8cdab898f4b (cherry picked from commit 7d80461067f71caa08fe0b4455840ba0a67ced32) Merged-In: Id92356c955fa0c67764d4b5248ffb8cdab898f4b --- .../SystemUI/src/com/android/keyguard/ClockEventController.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index b1ce54ebe099a..1d3eb94ff4d5b 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -96,6 +96,8 @@ open class ClockEventController @Inject constructor( if (regionSamplingEnabled) { clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener) clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener) + } else { + updateColors() } updateFontSizes() } From f5fe080dfb52bbd0ab3e9cbe965ea2e6c04989c9 Mon Sep 17 00:00:00 2001 From: Grace Cheng Date: Mon, 30 Jan 2023 19:44:20 +0000 Subject: [PATCH 02/74] Separates SFPS and UDFPS BP animation logic Fixes issue with UDFPS animation playing on transition from STATE_IDLE Test: Tested BP on rear-FPS, SFPS, UDFPS devices to check animations Fixes: 266367594 Change-Id: I8ed3371519fe3b2419c32829036bbb0c530e48e1 (cherry picked from commit af376971c876469bf268ff303a99ca75d3d0362e) Merged-In: I8ed3371519fe3b2419c32829036bbb0c530e48e1 --- .../AuthBiometricFingerprintIconController.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index 436f9dfb0d742..b896db26d6d15 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -104,7 +104,7 @@ open class AuthBiometricFingerprintIconController( iconView.frame = 0 iconViewOverlay.frame = 0 - if (shouldAnimateIconViewForTransition(lastState, newState)) { + if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) { iconView.playAnimation() } @@ -165,6 +165,18 @@ open class AuthBiometricFingerprintIconController( protected open fun shouldAnimateIconViewForTransition( @BiometricState oldState: Int, @BiometricState newState: Int + ) = when (newState) { + STATE_HELP, + STATE_ERROR -> true + STATE_AUTHENTICATING_ANIMATING_IN, + STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP + STATE_AUTHENTICATED -> true + else -> false + } + + private fun shouldAnimateSfpsIconViewForTransition( + @BiometricState oldState: Int, + @BiometricState newState: Int ) = when (newState) { STATE_HELP, STATE_ERROR -> true From 4accb472ce86d24d95ddddec3dcee75e8821319b Mon Sep 17 00:00:00 2001 From: Aritra Sen Date: Wed, 15 Feb 2023 18:20:49 +0000 Subject: [PATCH 03/74] Revert "AudioService: handle BT profile disconnections" This reverts commit a98ef735fe3ed3bd07dc0d7a60e413e984939185. Reason for revert: b/269238085 Change-Id: Ie76e86489ee079a315c9ad922ffd2bc101fd6a21 (cherry picked from commit 731d44fd97d83d6e7798762dea1b60e006e21d8a) Merged-In: Ie76e86489ee079a315c9ad922ffd2bc101fd6a21 --- .../server/audio/AudioDeviceBroker.java | 1 - .../com/android/server/audio/BtHelper.java | 68 ++----------------- 2 files changed, 7 insertions(+), 62 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index cf880eba20f70..0589cfc0967b6 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1512,7 +1512,6 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE: if (msg.arg1 != BluetoothProfile.HEADSET) { synchronized (mDeviceStateLock) { - mBtHelper.onBtProfileDisconnected(msg.arg1); mDeviceInventory.onBtProfileDisconnected(msg.arg1); } } else { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index f95982138564b..6cd42f87aedeb 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -279,11 +279,7 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); - try { - mA2dp.setAvrcpAbsoluteVolume(index); - } catch (Exception e) { - Log.e(TAG, "Exception while changing abs volume", e); - } + mA2dp.setAvrcpAbsoluteVolume(index); } /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec( @@ -291,12 +287,7 @@ public class BtHelper { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } - final BluetoothCodecStatus btCodecStatus = null; - try { - mA2dp.getCodecStatus(device); - } catch (Exception e) { - Log.e(TAG, "Exception while getting status of " + device, e); - } + final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); if (btCodecStatus == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } @@ -430,11 +421,7 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex)); - try { - mLeAudio.setVolume(volume); - } catch (Exception e) { - Log.e(TAG, "Exception while setting LE volume", e); - } + mLeAudio.setVolume(volume); } /*package*/ synchronized void setHearingAidVolume(int index, int streamType, @@ -460,11 +447,7 @@ public class BtHelper { AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); } - try { - mHearingAid.setVolume(gainDB); - } catch (Exception e) { - Log.i(TAG, "Exception while setting hearing aid volume", e); - } + mHearingAid.setVolume(gainDB); } /*package*/ synchronized void onBroadcastScoConnectionState(int state) { @@ -504,35 +487,6 @@ public class BtHelper { mBluetoothHeadset = null; } - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void onBtProfileDisconnected(int profile) { - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = null; - break; - case BluetoothProfile.HEARING_AID: - mHearingAid = null; - break; - case BluetoothProfile.LE_AUDIO: - mLeAudio = null; - break; - - case BluetoothProfile.A2DP_SINK: - case BluetoothProfile.LE_AUDIO_BROADCAST: - // shouldn't be received here as profile doesn't involve BtHelper - Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper " - + BluetoothProfile.getProfileName(profile)); - break; - - default: - // Not a valid profile to disconnect - Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " - + BluetoothProfile.getProfileName(profile)); - break; - } - } - - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { onHeadsetProfileConnected((BluetoothHeadset) proxy); @@ -718,6 +672,7 @@ public class BtHelper { public void onServiceConnected(int profile, BluetoothProfile proxy) { switch(profile) { case BluetoothProfile.A2DP: + case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: @@ -727,10 +682,6 @@ public class BtHelper { mDeviceBroker.postBtProfileConnected(profile, proxy); break; - case BluetoothProfile.A2DP_SINK: - // no A2DP sink functionality handled by BtHelper - case BluetoothProfile.LE_AUDIO_BROADCAST: - // no broadcast functionality handled by BtHelper default: break; } @@ -739,19 +690,14 @@ public class BtHelper { switch (profile) { case BluetoothProfile.A2DP: + case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: - AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( - "BT profile service: disconnecting " - + BluetoothProfile.getProfileName(profile) + " profile")); + case BluetoothProfile.LE_AUDIO_BROADCAST: mDeviceBroker.postBtProfileDisconnected(profile); break; - case BluetoothProfile.A2DP_SINK: - // no A2DP sink functionality handled by BtHelper - case BluetoothProfile.LE_AUDIO_BROADCAST: - // no broadcast functionality handled by BtHelper default: break; } From 8f3c4b2a9c8db9373284d82e3a0ffd5629d247e1 Mon Sep 17 00:00:00 2001 From: Aritra Sen Date: Wed, 15 Feb 2023 18:20:49 +0000 Subject: [PATCH 04/74] Revert "AudioService: handle BT profile disconnections" This reverts commit a98ef735fe3ed3bd07dc0d7a60e413e984939185. Reason for revert: b/269238085 Change-Id: Ie76e86489ee079a315c9ad922ffd2bc101fd6a21 Merged-In: Ie76e86489ee079a315c9ad922ffd2bc101fd6a21 (cherry picked from commit 185520316811e21d7dde82b85c1db18dd760c26a) Merged-In: Ie76e86489ee079a315c9ad922ffd2bc101fd6a21 --- .../server/audio/AudioDeviceBroker.java | 1 - .../com/android/server/audio/BtHelper.java | 68 ++----------------- 2 files changed, 7 insertions(+), 62 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index cf880eba20f70..0589cfc0967b6 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1512,7 +1512,6 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE: if (msg.arg1 != BluetoothProfile.HEADSET) { synchronized (mDeviceStateLock) { - mBtHelper.onBtProfileDisconnected(msg.arg1); mDeviceInventory.onBtProfileDisconnected(msg.arg1); } } else { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index f95982138564b..6cd42f87aedeb 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -279,11 +279,7 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); - try { - mA2dp.setAvrcpAbsoluteVolume(index); - } catch (Exception e) { - Log.e(TAG, "Exception while changing abs volume", e); - } + mA2dp.setAvrcpAbsoluteVolume(index); } /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec( @@ -291,12 +287,7 @@ public class BtHelper { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } - final BluetoothCodecStatus btCodecStatus = null; - try { - mA2dp.getCodecStatus(device); - } catch (Exception e) { - Log.e(TAG, "Exception while getting status of " + device, e); - } + final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); if (btCodecStatus == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } @@ -430,11 +421,7 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex)); - try { - mLeAudio.setVolume(volume); - } catch (Exception e) { - Log.e(TAG, "Exception while setting LE volume", e); - } + mLeAudio.setVolume(volume); } /*package*/ synchronized void setHearingAidVolume(int index, int streamType, @@ -460,11 +447,7 @@ public class BtHelper { AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); } - try { - mHearingAid.setVolume(gainDB); - } catch (Exception e) { - Log.i(TAG, "Exception while setting hearing aid volume", e); - } + mHearingAid.setVolume(gainDB); } /*package*/ synchronized void onBroadcastScoConnectionState(int state) { @@ -504,35 +487,6 @@ public class BtHelper { mBluetoothHeadset = null; } - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ synchronized void onBtProfileDisconnected(int profile) { - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = null; - break; - case BluetoothProfile.HEARING_AID: - mHearingAid = null; - break; - case BluetoothProfile.LE_AUDIO: - mLeAudio = null; - break; - - case BluetoothProfile.A2DP_SINK: - case BluetoothProfile.LE_AUDIO_BROADCAST: - // shouldn't be received here as profile doesn't involve BtHelper - Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper " - + BluetoothProfile.getProfileName(profile)); - break; - - default: - // Not a valid profile to disconnect - Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " - + BluetoothProfile.getProfileName(profile)); - break; - } - } - - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { onHeadsetProfileConnected((BluetoothHeadset) proxy); @@ -718,6 +672,7 @@ public class BtHelper { public void onServiceConnected(int profile, BluetoothProfile proxy) { switch(profile) { case BluetoothProfile.A2DP: + case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: @@ -727,10 +682,6 @@ public class BtHelper { mDeviceBroker.postBtProfileConnected(profile, proxy); break; - case BluetoothProfile.A2DP_SINK: - // no A2DP sink functionality handled by BtHelper - case BluetoothProfile.LE_AUDIO_BROADCAST: - // no broadcast functionality handled by BtHelper default: break; } @@ -739,19 +690,14 @@ public class BtHelper { switch (profile) { case BluetoothProfile.A2DP: + case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: - AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( - "BT profile service: disconnecting " - + BluetoothProfile.getProfileName(profile) + " profile")); + case BluetoothProfile.LE_AUDIO_BROADCAST: mDeviceBroker.postBtProfileDisconnected(profile); break; - case BluetoothProfile.A2DP_SINK: - // no A2DP sink functionality handled by BtHelper - case BluetoothProfile.LE_AUDIO_BROADCAST: - // no broadcast functionality handled by BtHelper default: break; } From c7e43e284b310b3533c2cf1025df7f2d0d1864b9 Mon Sep 17 00:00:00 2001 From: Neha Jain Date: Thu, 2 Mar 2023 21:31:21 +0000 Subject: [PATCH 05/74] Revert "[DO NOT MERGE] Transitions - Smooth out bouncer -> gone" Revert submission 21608738-mpietal_smooth_bouncer Reason for revert: b/271382454 Reverted changes: /q/submissionid:21608738-mpietal_smooth_bouncer Change-Id: I44fbb56be8faab54818dc2d1b6a93c264113bff9 (cherry picked from commit fcdc71cb2aea55b319e562c2dc2be8c639ac2c81) Merged-In: I44fbb56be8faab54818dc2d1b6a93c264113bff9 --- .../SystemUI/res-keyguard/values/dimens.xml | 2 +- .../keyguard/KeyguardSecurityContainer.java | 10 +- .../KeyguardSecurityContainerController.java | 7 +- .../data/repository/KeyguardRepository.kt | 28 ---- .../KeyguardTransitionRepository.kt | 26 ++-- .../FromDreamingTransitionInteractor.kt | 10 +- .../FromLockscreenTransitionInteractor.kt | 74 +++++----- .../FromPrimaryBouncerTransitionInteractor.kt | 42 ++---- .../domain/interactor/KeyguardInteractor.kt | 15 +- .../KeyguardTransitionAuditLogger.kt | 10 +- .../KeyguardTransitionInteractor.kt | 4 - .../ui/binder/KeyguardBouncerViewBinder.kt | 8 -- ...PrimaryBouncerToGoneTransitionViewModel.kt | 58 -------- ...NotificationShadeWindowViewController.java | 5 +- .../statusbar/phone/CentralSurfacesImpl.java | 6 - .../statusbar/phone/ScrimController.java | 60 +------- .../repository/KeyguardRepositoryImplTest.kt | 23 ---- .../KeyguardTransitionScenariosTest.kt | 103 +++++++++----- ...tificationShadeWindowViewControllerTest.kt | 129 ++++++++++-------- .../NotificationShadeWindowViewTest.java | 5 +- .../statusbar/phone/ScrimControllerTest.java | 41 +----- .../data/repository/FakeKeyguardRepository.kt | 3 - .../FakeKeyguardTransitionRepository.kt | 2 +- 23 files changed, 228 insertions(+), 443 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 992d143ff66d1..6cc5b9d7b7e88 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -78,7 +78,7 @@ - -50dp + -32dp 120dp diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index c6f0eeed108fc..f164e7d336428 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -39,6 +39,7 @@ import static java.lang.Integer.max; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; @@ -1043,10 +1044,13 @@ public class KeyguardSecurityContainer extends ConstraintLayout { int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); + AnimatorSet anims = new AnimatorSet(); ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); - yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE); - yAnim.setDuration(500); - yAnim.start(); + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f); + + anims.setInterpolator(Interpolators.STANDARD_ACCELERATE); + anims.playTogether(alphaAnim, yAnim); + anims.start(); } private void setupUserSwitcher() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index c32b8530d589f..b8bb2603fa035 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -636,17 +636,12 @@ public class KeyguardSecurityContainerController extends ViewController - /** Is the keyguard in a unlocked state? */ - val isKeyguardUnlocked: Flow - /** Is an activity showing over the keyguard? */ val isKeyguardOccluded: Flow @@ -281,31 +278,6 @@ constructor( } .distinctUntilChanged() - override val isKeyguardUnlocked: Flow = - conflatedCallbackFlow { - val callback = - object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "updated isKeyguardUnlocked" - ) - } - } - - keyguardStateController.addCallback(callback) - // Adding the callback does not send an initial update. - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "initial isKeyguardUnlocked" - ) - - awaitClose { keyguardStateController.removeCallback(callback) } - } - .distinctUntilChanged() - override val isKeyguardGoingAway: Flow = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 100bc596103dd..0c4bca616e12e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -68,11 +68,8 @@ interface KeyguardTransitionRepository { /** * Begin a transition from one state to another. Transitions are interruptible, and will issue a * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one. - * - * When canceled, there are two options: to continue from the current position of the prior - * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter. */ - fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID? + fun startTransition(info: TransitionInfo): UUID? /** * Allows manual control of a transition. When calling [startTransition], the consumer must pass @@ -133,10 +130,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio ) } - override fun startTransition( - info: TransitionInfo, - resetIfCanceled: Boolean, - ): UUID? { + override fun startTransition(info: TransitionInfo): UUID? { if (lastStep.from == info.from && lastStep.to == info.to) { Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return null @@ -144,11 +138,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val startingValue = if (lastStep.transitionState != TransitionState.FINISHED) { Log.i(TAG, "Transition still active: $lastStep, canceling") - if (resetIfCanceled) { - 0f - } else { - lastStep.value - } + lastStep.value } else { 0f } @@ -237,7 +227,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio } private fun trace(step: TransitionStep, isManual: Boolean) { - if (step.transitionState == TransitionState.RUNNING) { + if ( + step.transitionState != TransitionState.STARTED && + step.transitionState != TransitionState.FINISHED + ) { return } val traceName = @@ -250,10 +243,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val traceCookie = traceName.hashCode() if (step.transitionState == TransitionState.STARTED) { Trace.beginAsyncSection(traceName, traceCookie) - } else if ( - step.transitionState == TransitionState.FINISHED || - step.transitionState == TransitionState.CANCELED - ) { + } else if (step.transitionState == TransitionState.FINISHED) { Trace.endAsyncSection(traceName, traceCookie) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 3beac0b1322e3..8715d1f550697 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -56,7 +57,14 @@ constructor( private fun listenForDreamingToLockscreen() { scope.launch { - keyguardInteractor.isAbleToDream + // Dependending on the dream, either dream state or occluded change will change first, + // so listen for both + combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) { + isAbleToDream, + isKeyguardOccluded -> + isAbleToDream && isKeyguardOccluded + } + .distinctUntilChanged() .sample( combine( keyguardInteractor.dozeTransitionModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 911861ddde47b..d01f48970c97b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -130,59 +130,55 @@ constructor( shadeRepository.shadeModel .sample( combine( - keyguardTransitionInteractor.startedKeyguardTransitionStep, + keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - keyguardInteractor.isKeyguardUnlocked, - ::toTriple + ::Pair ), - ::toQuad + ::toTriple ) - .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) -> + .collect { (shadeModel, keyguardState, statusBarState) -> val id = transitionId if (id != null) { - if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) { - // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED or CANCELED - var nextState = - if (shadeModel.expansionAmount == 0f) { - TransitionState.FINISHED - } else if (shadeModel.expansionAmount == 1f) { - TransitionState.CANCELED - } else { - TransitionState.RUNNING - } - keyguardTransitionRepository.updateTransition( - id, - 1f - shadeModel.expansionAmount, - nextState, - ) - - if ( - nextState == TransitionState.CANCELED || - nextState == TransitionState.FINISHED - ) { - transitionId = null + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED or CANCELED + var nextState = + if (shadeModel.expansionAmount == 0f) { + TransitionState.FINISHED + } else if (shadeModel.expansionAmount == 1f) { + TransitionState.CANCELED + } else { + TransitionState.RUNNING } + keyguardTransitionRepository.updateTransition( + id, + 1f - shadeModel.expansionAmount, + nextState, + ) - // If canceled, just put the state back - if (nextState == TransitionState.CANCELED) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - ownerName = name, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.LOCKSCREEN, - animator = getAnimator(0.milliseconds) - ) + if ( + nextState == TransitionState.CANCELED || + nextState == TransitionState.FINISHED + ) { + transitionId = null + } + + // If canceled, just put the state back + if (nextState == TransitionState.CANCELED) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + animator = getAnimator(0.milliseconds) ) - } + ) } } else { // TODO (b/251849525): Remove statusbarstate check when that state is // integrated into KeyguardTransitionRepository if ( - keyguardState.to == KeyguardState.LOCKSCREEN && + keyguardState == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && - !isKeyguardUnlocked && statusBarState == KEYGUARD ) { transitionId = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 94961cbf4240d..b59b413d7a40e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -17,9 +17,6 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator -import com.android.keyguard.KeyguardSecurityModel -import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -29,8 +26,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.sample import javax.inject.Inject -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -42,8 +37,7 @@ constructor( @Application private val scope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val keyguardSecurityModel: KeyguardSecurityModel, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor ) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) { override fun start() { @@ -99,47 +93,31 @@ constructor( private fun listenForPrimaryBouncerToGone() { scope.launch { keyguardInteractor.isKeyguardGoingAway - .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { (isKeyguardGoingAway, lastStartedTransitionStep) -> - if ( - isKeyguardGoingAway && - lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER - ) { - val securityMode = - keyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser() - ) - // IME for password requires a slightly faster animation - val duration = - if (securityMode == Password) { - TO_GONE_SHORT_DURATION - } else { - TO_GONE_DURATION - } + .sample(keyguardTransitionInteractor.finishedKeyguardState) { a, b -> Pair(a, b) } + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (isKeyguardGoingAway && keyguardState == KeyguardState.PRIMARY_BOUNCER) { keyguardTransitionRepository.startTransition( TransitionInfo( ownerName = name, from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - animator = getAnimator(duration), - ), - resetIfCanceled = true, + animator = getAnimator(), + ) ) } } } } - private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { + private fun getAnimator(): ValueAnimator { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) - setDuration(duration.inWholeMilliseconds) + setDuration(TRANSITION_DURATION_MS) } } companion object { - private val DEFAULT_DURATION = 300.milliseconds - val TO_GONE_DURATION = 250.milliseconds - val TO_GONE_SHORT_DURATION = 200.milliseconds + private const val TRANSITION_DURATION_MS = 300L } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index ec99049b42e32..d25aff0add863 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -33,9 +33,7 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDoz import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel -import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -97,9 +95,6 @@ constructor( awaitClose { commandQueue.removeCallback(callback) } } - /** The device wake/sleep state */ - val wakefulnessModel: Flow = repository.wakefulness - /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means * that doze mode is not running and DREAMING is ok to commence. @@ -114,12 +109,6 @@ constructor( isDreaming && isDozeOff(dozeTransitionModel.to) } ) - .sample( - wakefulnessModel, - { isAbleToDream, wakefulnessModel -> - isAbleToDream && isWakingOrStartingToWake(wakefulnessModel) - } - ) .flatMapLatest { isAbleToDream -> flow { delay(50) @@ -130,8 +119,6 @@ constructor( /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow = repository.isKeyguardShowing - /** Whether the keyguard is unlocked or not. */ - val isKeyguardUnlocked: Flow = repository.isKeyguardUnlocked /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow = repository.isKeyguardOccluded /** Whether the keyguard is going away. */ @@ -140,6 +127,8 @@ constructor( val primaryBouncerShowing: Flow = bouncerRepository.primaryBouncerVisible /** Whether the alternate bouncer is showing or not. */ val alternateBouncerShowing: Flow = bouncerRepository.alternateBouncerVisible + /** The device wake/sleep state */ + val wakefulnessModel: Flow = repository.wakefulness /** Observable for the [StatusBarState] */ val statusBarState: Flow = repository.statusBarState /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index e650b9fc0e478..51b02779a89f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -61,15 +61,7 @@ constructor( } scope.launch { - keyguardInteractor.isAbleToDream.collect { - logger.log(TAG, VERBOSE, "isAbleToDream", it) - } - } - - scope.launch { - keyguardInteractor.isKeyguardOccluded.collect { - logger.log(TAG, VERBOSE, "isOccluded", it) - } + keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) } } scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 3c0ec350c5c5b..1b7da5b65a032 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -78,10 +78,6 @@ constructor( val occludedToLockscreenTransition: Flow = repository.transition(OCCLUDED, LOCKSCREEN) - /** PRIMARY_BOUNCER->GONE transition information. */ - val primaryBouncerToGoneTransition: Flow = - repository.transition(PRIMARY_BOUNCER, GONE) - /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 2337ffc35fa63..7db567b2a0e93 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -31,7 +31,6 @@ import com.android.settingslib.Utils import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import kotlinx.coroutines.awaitCancellation @@ -45,7 +44,6 @@ object KeyguardBouncerViewBinder { fun bind( view: ViewGroup, viewModel: KeyguardBouncerViewModel, - primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, componentFactory: KeyguardBouncerComponent.Factory ) { // Builds the KeyguardSecurityContainerController from bouncer view group. @@ -146,12 +144,6 @@ object KeyguardBouncerViewBinder { } } - launch { - primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha -> - securityContainerController.setAlpha(alpha) - } - } - launch { viewModel.bouncerExpansionAmount .filter { it == EXPANSION_VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt deleted file mode 100644 index 08907916a8c31..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds -import kotlinx.coroutines.flow.Flow - -/** - * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to - * consume. - */ -@SysUISingleton -class PrimaryBouncerToGoneTransitionViewModel -@Inject -constructor( - private val interactor: KeyguardTransitionInteractor, -) { - private val transitionAnimation = - KeyguardTransitionAnimationFlow( - transitionDuration = TO_GONE_DURATION, - transitionFlow = interactor.primaryBouncerToGoneTransition, - ) - - /** Bouncer container alpha */ - val bouncerAlpha: Flow = - transitionAnimation.createFlow( - duration = 200.milliseconds, - onStep = { 1f - it }, - ) - - /** Scrim alpha */ - val scrimAlpha: Flow = - transitionAnimation.createFlow( - duration = TO_GONE_DURATION, - interpolator = EMPHASIZED_ACCELERATE, - onStep = { 1f - it }, - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index c130b3913b640..87350b465895c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -45,7 +45,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -134,8 +133,7 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, AlternateBouncerInteractor alternateBouncerInteractor, - KeyguardTransitionInteractor keyguardTransitionInteractor, - PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel + KeyguardTransitionInteractor keyguardTransitionInteractor ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -162,7 +160,6 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewBinder.bind( mView.findViewById(R.id.keyguard_bouncer_container), keyguardBouncerViewModel, - primaryBouncerToGoneTransitionViewModel, keyguardBouncerComponentFactory); collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index b5d51ce2b9ee3..be791f9e2242d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3695,12 +3695,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void notifyBiometricAuthModeChanged() { mDozeServiceHost.updateDozing(); - if (mBiometricUnlockController.getMode() - == BiometricUnlockController.MODE_DISMISS_BOUNCER) { - // Don't update the scrim controller at this time, in favor of the transition repository - // updating the scrim - return; - } updateScrimController(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index c01137aad4084..8e0ec284c840a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; - import static java.lang.Float.isNaN; import android.animation.Animator; @@ -55,11 +53,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; -import com.android.systemui.keyguard.shared.model.TransitionState; -import com.android.systemui.keyguard.shared.model.TransitionStep; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -77,8 +71,6 @@ import java.util.function.Consumer; import javax.inject.Inject; -import kotlinx.coroutines.CoroutineDispatcher; - /** * Controls both the scrim behind the notifications and in front of the notifications (when a * security method gets shown). @@ -259,28 +251,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private boolean mWakeLockHeld; private boolean mKeyguardOccluded; - private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - private CoroutineDispatcher mMainDispatcher; - private boolean mIsBouncerToGoneTransitionStarted = false; - private boolean mIsBouncerToGoneTransitionRunning = false; - private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - private final Consumer mScrimAlphaConsumer = - (Float alpha) -> { - mScrimInFront.setViewAlpha(0f); - mNotificationsScrim.setViewAlpha(0f); - mScrimBehind.setViewAlpha(alpha); - }; - final Consumer mPrimaryBouncerToGoneTransition = - (TransitionStep step) -> { - mIsBouncerToGoneTransitionRunning = - step.getTransitionState() == TransitionState.RUNNING; - mIsBouncerToGoneTransitionStarted = - step.getTransitionState() == TransitionState.STARTED; - if (mIsBouncerToGoneTransitionStarted) { - transitionTo(ScrimState.UNLOCKED); - } - }; - @Inject public ScrimController( LightBarController lightBarController, @@ -295,10 +265,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump @Main Executor mainExecutor, ScreenOffAnimationController screenOffAnimationController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, - KeyguardTransitionInteractor keyguardTransitionInteractor, - @Main CoroutineDispatcher mainDispatcher) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; @@ -337,9 +304,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } }); mColors = new GradientColors(); - mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; - mKeyguardTransitionInteractor = keyguardTransitionInteractor; - mMainDispatcher = mainDispatcher; } /** @@ -379,11 +343,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump for (ScrimState state : ScrimState.values()) { state.prepare(state); } - - collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(), - mPrimaryBouncerToGoneTransition, mMainDispatcher); - collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), - mScrimAlphaConsumer, mMainDispatcher); } /** @@ -406,11 +365,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void transitionTo(ScrimState state, Callback callback) { - if (mIsBouncerToGoneTransitionRunning) { - Log.i(TAG, "Skipping transition to: " + state - + " while mIsBouncerToGoneTransitionRunning"); - return; - } if (state == mState) { // Call the callback anyway, unless it's already enqueued if (callback != null && mCallback != callback) { @@ -830,11 +784,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mBehindAlpha = 0; mNotificationsAlpha = 0; } else { + // Behind scrim will finish fading in at 30% expansion. float behindFraction = MathUtils .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction); - if (!mIsBouncerToGoneTransitionStarted) { - mBehindAlpha = behindFraction * mDefaultScrimAlpha; - } + mBehindAlpha = behindFraction * mDefaultScrimAlpha; // Delay fade-in of notification scrim a bit further, to coincide with the // behind scrim finishing fading in. // Also to coincide with the view starting to fade in, otherwise the empty @@ -1172,9 +1125,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", Color.alpha(tint)); scrimView.setTint(tint); - if (!mIsBouncerToGoneTransitionRunning) { - scrimView.setViewAlpha(alpha); - } + scrimView.setViewAlpha(alpha); } else { scrim.setAlpha(alpha); } @@ -1522,9 +1473,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void setKeyguardOccluded(boolean keyguardOccluded) { - if (mKeyguardOccluded == keyguardOccluded) { - return; - } mKeyguardOccluded = keyguardOccluded; updateScrims(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 0e6f8d4e0720e..0469e77ca9912 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -218,29 +218,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() } - @Test - fun isKeyguardUnlocked() = - runTest(UnconfinedTestDispatcher()) { - whenever(keyguardStateController.isUnlocked).thenReturn(false) - var latest: Boolean? = null - val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this) - - assertThat(latest).isFalse() - - val captor = argumentCaptor() - verify(keyguardStateController).addCallback(captor.capture()) - - whenever(keyguardStateController.isUnlocked).thenReturn(true) - captor.value.onUnlockedChanged() - assertThat(latest).isTrue() - - whenever(keyguardStateController.isUnlocked).thenReturn(false) - captor.value.onUnlockedChanged() - assertThat(latest).isFalse() - - job.cancel() - } - @Test fun isDozing() = runTest(UnconfinedTestDispatcher()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index fe9098fa5c259..ae7a928cdb2c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,8 +19,6 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardSecurityModel -import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators import com.android.systemui.flags.FakeFeatureFlags @@ -42,7 +40,6 @@ import com.android.systemui.keyguard.util.KeyguardTransitionRunner import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelChildren @@ -54,8 +51,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -82,7 +77,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // Used to verify transition requests for test output @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository @Mock private lateinit var commandQueue: CommandQueue - @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor @@ -108,8 +102,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { transitionRepository = KeyguardTransitionRepositoryImpl() runner = KeyguardTransitionRunner(transitionRepository) - whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) - val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( @@ -181,17 +173,16 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardInteractor = createKeyguardInteractor(featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), - keyguardSecurityModel = keyguardSecurityModel, ) fromPrimaryBouncerTransitionInteractor.start() } @Test - fun `DREAMING to LOCKSCREEN`() = + fun `DREAMING to LOCKSCREEN - dreaming state changes first`() = testScope.runTest { - // GIVEN a device is dreaming + // GIVEN a device is dreaming and occluded keyguardRepository.setDreamingWithOverlay(true) - keyguardRepository.setWakefulnessModel(startingToWake()) + keyguardRepository.setKeyguardOccluded(true) runCurrent() // GIVEN a prior transition has run to DREAMING @@ -224,7 +215,56 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to BOUNCER should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `DREAMING to LOCKSCREEN - occluded state changes first`() = + testScope.runTest { + // GIVEN a device is dreaming and occluded + keyguardRepository.setDreamingWithOverlay(true) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // GIVEN a prior transition has run to DREAMING + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN doze is complete + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + // AND occluded has stopped + keyguardRepository.setKeyguardOccluded(false) + advanceUntilIdle() + // AND then dreaming has stopped + keyguardRepository.setDreamingWithOverlay(false) + advanceUntilIdle() + + val info = + withArgCaptor { + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") @@ -264,7 +304,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to PRIMARY_BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -305,7 +345,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") @@ -346,7 +386,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") @@ -387,7 +427,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -428,7 +468,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -465,7 +505,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") @@ -502,7 +542,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") @@ -543,7 +583,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -584,7 +624,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -621,7 +661,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -637,7 +677,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a device that is not dreaming or dozing keyguardRepository.setDreamingWithOverlay(false) - keyguardRepository.setWakefulnessModel(startingToWake()) keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) @@ -665,7 +704,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DREAMING should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -702,7 +741,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to PRIMARY_BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -745,7 +784,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -789,7 +828,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -831,7 +870,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to LOCKSCREEN should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -873,7 +912,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") @@ -915,7 +954,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") @@ -956,7 +995,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to LOCKSCREEN should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 82a57438052fa..0a401b09b6cfb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationInsetsController @@ -66,32 +65,48 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) class NotificationShadeWindowViewControllerTest : SysuiTestCase() { - @Mock private lateinit var view: NotificationShadeWindowView - @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController - @Mock private lateinit var centralSurfaces: CentralSurfaces - @Mock private lateinit var dockManager: DockManager - @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController - @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController - @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController - @Mock private lateinit var ambientState: AmbientState - @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel - @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController - @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager - @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController + @Mock + private lateinit var view: NotificationShadeWindowView + @Mock + private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController + @Mock + private lateinit var centralSurfaces: CentralSurfaces + @Mock + private lateinit var dockManager: DockManager + @Mock + private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock + private lateinit var notificationShadeDepthController: NotificationShadeDepthController + @Mock + private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController + @Mock + private lateinit var ambientState: AmbientState + @Mock + private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock + private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController + @Mock + private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock + private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock private lateinit var lockIconViewController: LockIconViewController - @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController - @Mock private lateinit var pulsingGestureListener: PulsingGestureListener - @Mock private lateinit var notificationInsetsController: NotificationInsetsController - @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock + private lateinit var lockIconViewController: LockIconViewController + @Mock + private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController + @Mock + private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock + private lateinit var notificationInsetsController: NotificationInsetsController + @Mock + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor - @Mock - lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel private lateinit var interactionEventHandlerCaptor: ArgumentCaptor private lateinit var interactionEventHandler: InteractionEventHandler @@ -103,44 +118,43 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) whenever(view.findViewById(R.id.keyguard_bouncer_container)) - .thenReturn(mock(ViewGroup::class.java)) + .thenReturn(mock(ViewGroup::class.java)) whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) - .thenReturn(keyguardBouncerComponent) + .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) - .thenReturn(keyguardSecurityContainerController) + .thenReturn(keyguardSecurityContainerController) whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) - .thenReturn(emptyFlow()) - underTest = - NotificationShadeWindowViewController( - lockscreenShadeTransitionController, - FalsingCollectorFake(), - sysuiStatusBarStateController, - dockManager, - notificationShadeDepthController, - view, - notificationPanelViewController, - ShadeExpansionStateManager(), - stackScrollLayoutController, - statusBarKeyguardViewManager, - statusBarWindowStateController, - lockIconViewController, - centralSurfaces, - notificationShadeWindowController, - keyguardUnlockAnimationController, - notificationInsetsController, - ambientState, - pulsingGestureListener, - keyguardBouncerViewModel, - keyguardBouncerComponentFactory, - alternateBouncerInteractor, - keyguardTransitionInteractor, - primaryBouncerToGoneTransitionViewModel, - ) + .thenReturn(emptyFlow()) + underTest = NotificationShadeWindowViewController( + lockscreenShadeTransitionController, + FalsingCollectorFake(), + sysuiStatusBarStateController, + dockManager, + notificationShadeDepthController, + view, + notificationPanelViewController, + ShadeExpansionStateManager(), + stackScrollLayoutController, + statusBarKeyguardViewManager, + statusBarWindowStateController, + lockIconViewController, + centralSurfaces, + notificationShadeWindowController, + keyguardUnlockAnimationController, + notificationInsetsController, + ambientState, + pulsingGestureListener, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory, + alternateBouncerInteractor, + keyguardTransitionInteractor, + ) underTest.setupExpandedStatusBar() - interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java) + interactionEventHandlerCaptor = + ArgumentCaptor.forClass(InteractionEventHandler::class.java) verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) - interactionEventHandler = interactionEventHandlerCaptor.value + interactionEventHandler = interactionEventHandlerCaptor.value } // Note: So far, these tests only cover interactions with the status bar view controller. More @@ -170,11 +184,14 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { underTest.setStatusBarViewController(phoneStatusBarViewController) - val downEvBelow = - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + val downEvBelow = MotionEvent.obtain( + 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0 + ) interactionEventHandler.handleDispatchTouchEvent(downEvBelow) - val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) + val nextEvent = MotionEvent.obtain( + 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0 + ) whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index faa6221b675c0..5d719790386a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -46,7 +46,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -102,7 +101,6 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationInsetsController mNotificationInsetsController; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @Captor private ArgumentCaptor mInteractionEventHandlerCaptor; @@ -152,8 +150,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mKeyguardBouncerViewModel, mKeyguardBouncerComponentFactory, mAlternateBouncerInteractor, - mKeyguardTransitionInteractor, - mPrimaryBouncerToGoneTransitionViewModel + mKeyguardTransitionInteractor ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 180d9f8956c32..dc5a0472f49e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -24,8 +24,6 @@ import static com.android.systemui.statusbar.phone.ScrimState.SHADE_LOCKED; import static com.google.common.truth.Truth.assertThat; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -60,12 +58,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.keyguard.shared.model.TransitionState; -import com.android.systemui.keyguard.shared.model.TransitionStep; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -92,10 +85,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import kotlinx.coroutines.CoroutineDispatcher; - @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) +@TestableLooper.RunWithLooper @SmallTest public class ScrimControllerTest extends SysuiTestCase { @@ -124,10 +115,6 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private DockManager mDockManager; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private CoroutineDispatcher mMainDispatcher; - // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The // event-dispatch-on-registration pattern caused some of these unit tests to fail.) @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -238,20 +225,13 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock); when(mDockManager.isDocked()).thenReturn(false); - when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition()) - .thenReturn(emptyFlow()); - when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha()).thenReturn(emptyFlow()); - mScrimController = new ScrimController(mLightBarController, mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), mScreenOffAnimationController, mKeyguardUnlockAnimationController, - mStatusBarKeyguardViewManager, - mPrimaryBouncerToGoneTransitionViewModel, - mKeyguardTransitionInteractor, - mMainDispatcher); + mStatusBarKeyguardViewManager); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -881,10 +861,7 @@ public class ScrimControllerTest extends SysuiTestCase { mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), mScreenOffAnimationController, mKeyguardUnlockAnimationController, - mStatusBarKeyguardViewManager, - mPrimaryBouncerToGoneTransitionViewModel, - mKeyguardTransitionInteractor, - mMainDispatcher); + mStatusBarKeyguardViewManager); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -1652,18 +1629,6 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimAlpha(mScrimBehind, 0); } - @Test - public void ignoreTransitionRequestWhileKeyguardTransitionRunning() { - mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.mPrimaryBouncerToGoneTransition.accept( - new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, - TransitionState.RUNNING, "ScrimControllerTest")); - - // This request should not happen - mScrimController.transitionTo(ScrimState.BOUNCER); - assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED); - } - private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) { mScrimController.setRawPanelExpansionFraction(expansion); finishAnimationsImmediately(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 194ed02712b20..1a371c73550c3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -47,9 +47,6 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow = _isKeyguardShowing - private val _isKeyguardUnlocked = MutableStateFlow(false) - override val isKeyguardUnlocked: Flow = _isKeyguardUnlocked - private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow = _isKeyguardOccluded diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index 16442bb525b68..eac1bd1450333 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -37,7 +37,7 @@ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { _transitions.emit(step) } - override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? { + override fun startTransition(info: TransitionInfo): UUID? { return null } From 1a93f0f8a06a9a78a491a3884d52af774334fab5 Mon Sep 17 00:00:00 2001 From: Neha Jain Date: Thu, 2 Mar 2023 21:31:21 +0000 Subject: [PATCH 06/74] Revert "[DO NOT MERGE] Transitions - Smooth out bouncer -> gone" Revert submission 21608738-mpietal_smooth_bouncer Reason for revert: b/271382454 Reverted changes: /q/submissionid:21608738-mpietal_smooth_bouncer Change-Id: I44fbb56be8faab54818dc2d1b6a93c264113bff9 (cherry picked from commit fcdc71cb2aea55b319e562c2dc2be8c639ac2c81) Merged-In: I44fbb56be8faab54818dc2d1b6a93c264113bff9 --- .../SystemUI/res-keyguard/values/dimens.xml | 2 +- .../keyguard/KeyguardSecurityContainer.java | 10 +- .../KeyguardSecurityContainerController.java | 7 +- .../data/repository/KeyguardRepository.kt | 28 ---- .../KeyguardTransitionRepository.kt | 26 ++-- .../FromDreamingTransitionInteractor.kt | 10 +- .../FromLockscreenTransitionInteractor.kt | 74 +++++----- .../FromPrimaryBouncerTransitionInteractor.kt | 42 ++---- .../domain/interactor/KeyguardInteractor.kt | 15 +- .../KeyguardTransitionAuditLogger.kt | 10 +- .../KeyguardTransitionInteractor.kt | 4 - .../ui/binder/KeyguardBouncerViewBinder.kt | 8 -- ...PrimaryBouncerToGoneTransitionViewModel.kt | 58 -------- ...NotificationShadeWindowViewController.java | 5 +- .../statusbar/phone/CentralSurfacesImpl.java | 6 - .../statusbar/phone/ScrimController.java | 60 +------- .../repository/KeyguardRepositoryImplTest.kt | 23 ---- .../KeyguardTransitionScenariosTest.kt | 103 +++++++++----- ...tificationShadeWindowViewControllerTest.kt | 129 ++++++++++-------- .../NotificationShadeWindowViewTest.java | 5 +- .../statusbar/phone/ScrimControllerTest.java | 41 +----- .../data/repository/FakeKeyguardRepository.kt | 3 - .../FakeKeyguardTransitionRepository.kt | 2 +- 23 files changed, 228 insertions(+), 443 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 992d143ff66d1..6cc5b9d7b7e88 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -78,7 +78,7 @@ - -50dp + -32dp 120dp diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 66d5d097ab04f..29496169e04f4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -39,6 +39,7 @@ import static java.lang.Integer.max; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; @@ -1067,10 +1068,13 @@ public class KeyguardSecurityContainer extends ConstraintLayout { int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); + AnimatorSet anims = new AnimatorSet(); ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); - yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE); - yAnim.setDuration(500); - yAnim.start(); + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f); + + anims.setInterpolator(Interpolators.STANDARD_ACCELERATE); + anims.playTogether(alphaAnim, yAnim); + anims.start(); } private void setupUserSwitcher() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index c32b8530d589f..b8bb2603fa035 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -636,17 +636,12 @@ public class KeyguardSecurityContainerController extends ViewController - /** Is the keyguard in a unlocked state? */ - val isKeyguardUnlocked: Flow - /** Is an activity showing over the keyguard? */ val isKeyguardOccluded: Flow @@ -281,31 +278,6 @@ constructor( } .distinctUntilChanged() - override val isKeyguardUnlocked: Flow = - conflatedCallbackFlow { - val callback = - object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "updated isKeyguardUnlocked" - ) - } - } - - keyguardStateController.addCallback(callback) - // Adding the callback does not send an initial update. - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "initial isKeyguardUnlocked" - ) - - awaitClose { keyguardStateController.removeCallback(callback) } - } - .distinctUntilChanged() - override val isKeyguardGoingAway: Flow = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 100bc596103dd..0c4bca616e12e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -68,11 +68,8 @@ interface KeyguardTransitionRepository { /** * Begin a transition from one state to another. Transitions are interruptible, and will issue a * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one. - * - * When canceled, there are two options: to continue from the current position of the prior - * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter. */ - fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID? + fun startTransition(info: TransitionInfo): UUID? /** * Allows manual control of a transition. When calling [startTransition], the consumer must pass @@ -133,10 +130,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio ) } - override fun startTransition( - info: TransitionInfo, - resetIfCanceled: Boolean, - ): UUID? { + override fun startTransition(info: TransitionInfo): UUID? { if (lastStep.from == info.from && lastStep.to == info.to) { Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return null @@ -144,11 +138,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val startingValue = if (lastStep.transitionState != TransitionState.FINISHED) { Log.i(TAG, "Transition still active: $lastStep, canceling") - if (resetIfCanceled) { - 0f - } else { - lastStep.value - } + lastStep.value } else { 0f } @@ -237,7 +227,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio } private fun trace(step: TransitionStep, isManual: Boolean) { - if (step.transitionState == TransitionState.RUNNING) { + if ( + step.transitionState != TransitionState.STARTED && + step.transitionState != TransitionState.FINISHED + ) { return } val traceName = @@ -250,10 +243,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val traceCookie = traceName.hashCode() if (step.transitionState == TransitionState.STARTED) { Trace.beginAsyncSection(traceName, traceCookie) - } else if ( - step.transitionState == TransitionState.FINISHED || - step.transitionState == TransitionState.CANCELED - ) { + } else if (step.transitionState == TransitionState.FINISHED) { Trace.endAsyncSection(traceName, traceCookie) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 3beac0b1322e3..8715d1f550697 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -56,7 +57,14 @@ constructor( private fun listenForDreamingToLockscreen() { scope.launch { - keyguardInteractor.isAbleToDream + // Dependending on the dream, either dream state or occluded change will change first, + // so listen for both + combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) { + isAbleToDream, + isKeyguardOccluded -> + isAbleToDream && isKeyguardOccluded + } + .distinctUntilChanged() .sample( combine( keyguardInteractor.dozeTransitionModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 911861ddde47b..d01f48970c97b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -130,59 +130,55 @@ constructor( shadeRepository.shadeModel .sample( combine( - keyguardTransitionInteractor.startedKeyguardTransitionStep, + keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - keyguardInteractor.isKeyguardUnlocked, - ::toTriple + ::Pair ), - ::toQuad + ::toTriple ) - .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) -> + .collect { (shadeModel, keyguardState, statusBarState) -> val id = transitionId if (id != null) { - if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) { - // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED or CANCELED - var nextState = - if (shadeModel.expansionAmount == 0f) { - TransitionState.FINISHED - } else if (shadeModel.expansionAmount == 1f) { - TransitionState.CANCELED - } else { - TransitionState.RUNNING - } - keyguardTransitionRepository.updateTransition( - id, - 1f - shadeModel.expansionAmount, - nextState, - ) - - if ( - nextState == TransitionState.CANCELED || - nextState == TransitionState.FINISHED - ) { - transitionId = null + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED or CANCELED + var nextState = + if (shadeModel.expansionAmount == 0f) { + TransitionState.FINISHED + } else if (shadeModel.expansionAmount == 1f) { + TransitionState.CANCELED + } else { + TransitionState.RUNNING } + keyguardTransitionRepository.updateTransition( + id, + 1f - shadeModel.expansionAmount, + nextState, + ) - // If canceled, just put the state back - if (nextState == TransitionState.CANCELED) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - ownerName = name, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.LOCKSCREEN, - animator = getAnimator(0.milliseconds) - ) + if ( + nextState == TransitionState.CANCELED || + nextState == TransitionState.FINISHED + ) { + transitionId = null + } + + // If canceled, just put the state back + if (nextState == TransitionState.CANCELED) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + animator = getAnimator(0.milliseconds) ) - } + ) } } else { // TODO (b/251849525): Remove statusbarstate check when that state is // integrated into KeyguardTransitionRepository if ( - keyguardState.to == KeyguardState.LOCKSCREEN && + keyguardState == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && - !isKeyguardUnlocked && statusBarState == KEYGUARD ) { transitionId = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 94961cbf4240d..b59b413d7a40e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -17,9 +17,6 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator -import com.android.keyguard.KeyguardSecurityModel -import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -29,8 +26,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.sample import javax.inject.Inject -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -42,8 +37,7 @@ constructor( @Application private val scope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val keyguardSecurityModel: KeyguardSecurityModel, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor ) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) { override fun start() { @@ -99,47 +93,31 @@ constructor( private fun listenForPrimaryBouncerToGone() { scope.launch { keyguardInteractor.isKeyguardGoingAway - .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { (isKeyguardGoingAway, lastStartedTransitionStep) -> - if ( - isKeyguardGoingAway && - lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER - ) { - val securityMode = - keyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser() - ) - // IME for password requires a slightly faster animation - val duration = - if (securityMode == Password) { - TO_GONE_SHORT_DURATION - } else { - TO_GONE_DURATION - } + .sample(keyguardTransitionInteractor.finishedKeyguardState) { a, b -> Pair(a, b) } + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (isKeyguardGoingAway && keyguardState == KeyguardState.PRIMARY_BOUNCER) { keyguardTransitionRepository.startTransition( TransitionInfo( ownerName = name, from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - animator = getAnimator(duration), - ), - resetIfCanceled = true, + animator = getAnimator(), + ) ) } } } } - private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { + private fun getAnimator(): ValueAnimator { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) - setDuration(duration.inWholeMilliseconds) + setDuration(TRANSITION_DURATION_MS) } } companion object { - private val DEFAULT_DURATION = 300.milliseconds - val TO_GONE_DURATION = 250.milliseconds - val TO_GONE_SHORT_DURATION = 200.milliseconds + private const val TRANSITION_DURATION_MS = 300L } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index ec99049b42e32..d25aff0add863 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -33,9 +33,7 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDoz import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel -import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -97,9 +95,6 @@ constructor( awaitClose { commandQueue.removeCallback(callback) } } - /** The device wake/sleep state */ - val wakefulnessModel: Flow = repository.wakefulness - /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means * that doze mode is not running and DREAMING is ok to commence. @@ -114,12 +109,6 @@ constructor( isDreaming && isDozeOff(dozeTransitionModel.to) } ) - .sample( - wakefulnessModel, - { isAbleToDream, wakefulnessModel -> - isAbleToDream && isWakingOrStartingToWake(wakefulnessModel) - } - ) .flatMapLatest { isAbleToDream -> flow { delay(50) @@ -130,8 +119,6 @@ constructor( /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow = repository.isKeyguardShowing - /** Whether the keyguard is unlocked or not. */ - val isKeyguardUnlocked: Flow = repository.isKeyguardUnlocked /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow = repository.isKeyguardOccluded /** Whether the keyguard is going away. */ @@ -140,6 +127,8 @@ constructor( val primaryBouncerShowing: Flow = bouncerRepository.primaryBouncerVisible /** Whether the alternate bouncer is showing or not. */ val alternateBouncerShowing: Flow = bouncerRepository.alternateBouncerVisible + /** The device wake/sleep state */ + val wakefulnessModel: Flow = repository.wakefulness /** Observable for the [StatusBarState] */ val statusBarState: Flow = repository.statusBarState /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index e650b9fc0e478..51b02779a89f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -61,15 +61,7 @@ constructor( } scope.launch { - keyguardInteractor.isAbleToDream.collect { - logger.log(TAG, VERBOSE, "isAbleToDream", it) - } - } - - scope.launch { - keyguardInteractor.isKeyguardOccluded.collect { - logger.log(TAG, VERBOSE, "isOccluded", it) - } + keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) } } scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 3c0ec350c5c5b..1b7da5b65a032 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -78,10 +78,6 @@ constructor( val occludedToLockscreenTransition: Flow = repository.transition(OCCLUDED, LOCKSCREEN) - /** PRIMARY_BOUNCER->GONE transition information. */ - val primaryBouncerToGoneTransition: Flow = - repository.transition(PRIMARY_BOUNCER, GONE) - /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 2337ffc35fa63..7db567b2a0e93 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -31,7 +31,6 @@ import com.android.settingslib.Utils import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import kotlinx.coroutines.awaitCancellation @@ -45,7 +44,6 @@ object KeyguardBouncerViewBinder { fun bind( view: ViewGroup, viewModel: KeyguardBouncerViewModel, - primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, componentFactory: KeyguardBouncerComponent.Factory ) { // Builds the KeyguardSecurityContainerController from bouncer view group. @@ -146,12 +144,6 @@ object KeyguardBouncerViewBinder { } } - launch { - primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha -> - securityContainerController.setAlpha(alpha) - } - } - launch { viewModel.bouncerExpansionAmount .filter { it == EXPANSION_VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt deleted file mode 100644 index 08907916a8c31..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds -import kotlinx.coroutines.flow.Flow - -/** - * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to - * consume. - */ -@SysUISingleton -class PrimaryBouncerToGoneTransitionViewModel -@Inject -constructor( - private val interactor: KeyguardTransitionInteractor, -) { - private val transitionAnimation = - KeyguardTransitionAnimationFlow( - transitionDuration = TO_GONE_DURATION, - transitionFlow = interactor.primaryBouncerToGoneTransition, - ) - - /** Bouncer container alpha */ - val bouncerAlpha: Flow = - transitionAnimation.createFlow( - duration = 200.milliseconds, - onStep = { 1f - it }, - ) - - /** Scrim alpha */ - val scrimAlpha: Flow = - transitionAnimation.createFlow( - duration = TO_GONE_DURATION, - interpolator = EMPHASIZED_ACCELERATE, - onStep = { 1f - it }, - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index c130b3913b640..87350b465895c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -45,7 +45,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -134,8 +133,7 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, AlternateBouncerInteractor alternateBouncerInteractor, - KeyguardTransitionInteractor keyguardTransitionInteractor, - PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel + KeyguardTransitionInteractor keyguardTransitionInteractor ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -162,7 +160,6 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewBinder.bind( mView.findViewById(R.id.keyguard_bouncer_container), keyguardBouncerViewModel, - primaryBouncerToGoneTransitionViewModel, keyguardBouncerComponentFactory); collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index b5d51ce2b9ee3..be791f9e2242d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3695,12 +3695,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void notifyBiometricAuthModeChanged() { mDozeServiceHost.updateDozing(); - if (mBiometricUnlockController.getMode() - == BiometricUnlockController.MODE_DISMISS_BOUNCER) { - // Don't update the scrim controller at this time, in favor of the transition repository - // updating the scrim - return; - } updateScrimController(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index c01137aad4084..8e0ec284c840a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; - import static java.lang.Float.isNaN; import android.animation.Animator; @@ -55,11 +53,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; -import com.android.systemui.keyguard.shared.model.TransitionState; -import com.android.systemui.keyguard.shared.model.TransitionStep; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -77,8 +71,6 @@ import java.util.function.Consumer; import javax.inject.Inject; -import kotlinx.coroutines.CoroutineDispatcher; - /** * Controls both the scrim behind the notifications and in front of the notifications (when a * security method gets shown). @@ -259,28 +251,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private boolean mWakeLockHeld; private boolean mKeyguardOccluded; - private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - private CoroutineDispatcher mMainDispatcher; - private boolean mIsBouncerToGoneTransitionStarted = false; - private boolean mIsBouncerToGoneTransitionRunning = false; - private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - private final Consumer mScrimAlphaConsumer = - (Float alpha) -> { - mScrimInFront.setViewAlpha(0f); - mNotificationsScrim.setViewAlpha(0f); - mScrimBehind.setViewAlpha(alpha); - }; - final Consumer mPrimaryBouncerToGoneTransition = - (TransitionStep step) -> { - mIsBouncerToGoneTransitionRunning = - step.getTransitionState() == TransitionState.RUNNING; - mIsBouncerToGoneTransitionStarted = - step.getTransitionState() == TransitionState.STARTED; - if (mIsBouncerToGoneTransitionStarted) { - transitionTo(ScrimState.UNLOCKED); - } - }; - @Inject public ScrimController( LightBarController lightBarController, @@ -295,10 +265,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump @Main Executor mainExecutor, ScreenOffAnimationController screenOffAnimationController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, - KeyguardTransitionInteractor keyguardTransitionInteractor, - @Main CoroutineDispatcher mainDispatcher) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; @@ -337,9 +304,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } }); mColors = new GradientColors(); - mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; - mKeyguardTransitionInteractor = keyguardTransitionInteractor; - mMainDispatcher = mainDispatcher; } /** @@ -379,11 +343,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump for (ScrimState state : ScrimState.values()) { state.prepare(state); } - - collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(), - mPrimaryBouncerToGoneTransition, mMainDispatcher); - collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), - mScrimAlphaConsumer, mMainDispatcher); } /** @@ -406,11 +365,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void transitionTo(ScrimState state, Callback callback) { - if (mIsBouncerToGoneTransitionRunning) { - Log.i(TAG, "Skipping transition to: " + state - + " while mIsBouncerToGoneTransitionRunning"); - return; - } if (state == mState) { // Call the callback anyway, unless it's already enqueued if (callback != null && mCallback != callback) { @@ -830,11 +784,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mBehindAlpha = 0; mNotificationsAlpha = 0; } else { + // Behind scrim will finish fading in at 30% expansion. float behindFraction = MathUtils .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction); - if (!mIsBouncerToGoneTransitionStarted) { - mBehindAlpha = behindFraction * mDefaultScrimAlpha; - } + mBehindAlpha = behindFraction * mDefaultScrimAlpha; // Delay fade-in of notification scrim a bit further, to coincide with the // behind scrim finishing fading in. // Also to coincide with the view starting to fade in, otherwise the empty @@ -1172,9 +1125,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", Color.alpha(tint)); scrimView.setTint(tint); - if (!mIsBouncerToGoneTransitionRunning) { - scrimView.setViewAlpha(alpha); - } + scrimView.setViewAlpha(alpha); } else { scrim.setAlpha(alpha); } @@ -1522,9 +1473,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void setKeyguardOccluded(boolean keyguardOccluded) { - if (mKeyguardOccluded == keyguardOccluded) { - return; - } mKeyguardOccluded = keyguardOccluded; updateScrims(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 0e6f8d4e0720e..0469e77ca9912 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -218,29 +218,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() } - @Test - fun isKeyguardUnlocked() = - runTest(UnconfinedTestDispatcher()) { - whenever(keyguardStateController.isUnlocked).thenReturn(false) - var latest: Boolean? = null - val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this) - - assertThat(latest).isFalse() - - val captor = argumentCaptor() - verify(keyguardStateController).addCallback(captor.capture()) - - whenever(keyguardStateController.isUnlocked).thenReturn(true) - captor.value.onUnlockedChanged() - assertThat(latest).isTrue() - - whenever(keyguardStateController.isUnlocked).thenReturn(false) - captor.value.onUnlockedChanged() - assertThat(latest).isFalse() - - job.cancel() - } - @Test fun isDozing() = runTest(UnconfinedTestDispatcher()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index fe9098fa5c259..ae7a928cdb2c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,8 +19,6 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardSecurityModel -import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators import com.android.systemui.flags.FakeFeatureFlags @@ -42,7 +40,6 @@ import com.android.systemui.keyguard.util.KeyguardTransitionRunner import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelChildren @@ -54,8 +51,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -82,7 +77,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // Used to verify transition requests for test output @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository @Mock private lateinit var commandQueue: CommandQueue - @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor @@ -108,8 +102,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { transitionRepository = KeyguardTransitionRepositoryImpl() runner = KeyguardTransitionRunner(transitionRepository) - whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) - val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( @@ -181,17 +173,16 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardInteractor = createKeyguardInteractor(featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), - keyguardSecurityModel = keyguardSecurityModel, ) fromPrimaryBouncerTransitionInteractor.start() } @Test - fun `DREAMING to LOCKSCREEN`() = + fun `DREAMING to LOCKSCREEN - dreaming state changes first`() = testScope.runTest { - // GIVEN a device is dreaming + // GIVEN a device is dreaming and occluded keyguardRepository.setDreamingWithOverlay(true) - keyguardRepository.setWakefulnessModel(startingToWake()) + keyguardRepository.setKeyguardOccluded(true) runCurrent() // GIVEN a prior transition has run to DREAMING @@ -224,7 +215,56 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to BOUNCER should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `DREAMING to LOCKSCREEN - occluded state changes first`() = + testScope.runTest { + // GIVEN a device is dreaming and occluded + keyguardRepository.setDreamingWithOverlay(true) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // GIVEN a prior transition has run to DREAMING + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN doze is complete + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + // AND occluded has stopped + keyguardRepository.setKeyguardOccluded(false) + advanceUntilIdle() + // AND then dreaming has stopped + keyguardRepository.setDreamingWithOverlay(false) + advanceUntilIdle() + + val info = + withArgCaptor { + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") @@ -264,7 +304,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to PRIMARY_BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -305,7 +345,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") @@ -346,7 +386,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") @@ -387,7 +427,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -428,7 +468,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -465,7 +505,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") @@ -502,7 +542,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") @@ -543,7 +583,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -584,7 +624,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -621,7 +661,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -637,7 +677,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a device that is not dreaming or dozing keyguardRepository.setDreamingWithOverlay(false) - keyguardRepository.setWakefulnessModel(startingToWake()) keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) @@ -665,7 +704,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DREAMING should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -702,7 +741,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to PRIMARY_BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -745,7 +784,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -789,7 +828,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -831,7 +870,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to LOCKSCREEN should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -873,7 +912,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") @@ -915,7 +954,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") @@ -956,7 +995,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor { - verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + verify(mockTransitionRepository).startTransition(capture()) } // THEN a transition to LOCKSCREEN should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 82a57438052fa..0a401b09b6cfb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationInsetsController @@ -66,32 +65,48 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) class NotificationShadeWindowViewControllerTest : SysuiTestCase() { - @Mock private lateinit var view: NotificationShadeWindowView - @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController - @Mock private lateinit var centralSurfaces: CentralSurfaces - @Mock private lateinit var dockManager: DockManager - @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController - @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController - @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController - @Mock private lateinit var ambientState: AmbientState - @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel - @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController - @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager - @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController + @Mock + private lateinit var view: NotificationShadeWindowView + @Mock + private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController + @Mock + private lateinit var centralSurfaces: CentralSurfaces + @Mock + private lateinit var dockManager: DockManager + @Mock + private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock + private lateinit var notificationShadeDepthController: NotificationShadeDepthController + @Mock + private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController + @Mock + private lateinit var ambientState: AmbientState + @Mock + private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock + private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController + @Mock + private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock + private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock private lateinit var lockIconViewController: LockIconViewController - @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController - @Mock private lateinit var pulsingGestureListener: PulsingGestureListener - @Mock private lateinit var notificationInsetsController: NotificationInsetsController - @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock + private lateinit var lockIconViewController: LockIconViewController + @Mock + private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController + @Mock + private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock + private lateinit var notificationInsetsController: NotificationInsetsController + @Mock + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor - @Mock - lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel private lateinit var interactionEventHandlerCaptor: ArgumentCaptor private lateinit var interactionEventHandler: InteractionEventHandler @@ -103,44 +118,43 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) whenever(view.findViewById(R.id.keyguard_bouncer_container)) - .thenReturn(mock(ViewGroup::class.java)) + .thenReturn(mock(ViewGroup::class.java)) whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) - .thenReturn(keyguardBouncerComponent) + .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) - .thenReturn(keyguardSecurityContainerController) + .thenReturn(keyguardSecurityContainerController) whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) - .thenReturn(emptyFlow()) - underTest = - NotificationShadeWindowViewController( - lockscreenShadeTransitionController, - FalsingCollectorFake(), - sysuiStatusBarStateController, - dockManager, - notificationShadeDepthController, - view, - notificationPanelViewController, - ShadeExpansionStateManager(), - stackScrollLayoutController, - statusBarKeyguardViewManager, - statusBarWindowStateController, - lockIconViewController, - centralSurfaces, - notificationShadeWindowController, - keyguardUnlockAnimationController, - notificationInsetsController, - ambientState, - pulsingGestureListener, - keyguardBouncerViewModel, - keyguardBouncerComponentFactory, - alternateBouncerInteractor, - keyguardTransitionInteractor, - primaryBouncerToGoneTransitionViewModel, - ) + .thenReturn(emptyFlow()) + underTest = NotificationShadeWindowViewController( + lockscreenShadeTransitionController, + FalsingCollectorFake(), + sysuiStatusBarStateController, + dockManager, + notificationShadeDepthController, + view, + notificationPanelViewController, + ShadeExpansionStateManager(), + stackScrollLayoutController, + statusBarKeyguardViewManager, + statusBarWindowStateController, + lockIconViewController, + centralSurfaces, + notificationShadeWindowController, + keyguardUnlockAnimationController, + notificationInsetsController, + ambientState, + pulsingGestureListener, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory, + alternateBouncerInteractor, + keyguardTransitionInteractor, + ) underTest.setupExpandedStatusBar() - interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java) + interactionEventHandlerCaptor = + ArgumentCaptor.forClass(InteractionEventHandler::class.java) verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) - interactionEventHandler = interactionEventHandlerCaptor.value + interactionEventHandler = interactionEventHandlerCaptor.value } // Note: So far, these tests only cover interactions with the status bar view controller. More @@ -170,11 +184,14 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { underTest.setStatusBarViewController(phoneStatusBarViewController) - val downEvBelow = - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + val downEvBelow = MotionEvent.obtain( + 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0 + ) interactionEventHandler.handleDispatchTouchEvent(downEvBelow) - val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) + val nextEvent = MotionEvent.obtain( + 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0 + ) whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index faa6221b675c0..5d719790386a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -46,7 +46,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -102,7 +101,6 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationInsetsController mNotificationInsetsController; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @Captor private ArgumentCaptor mInteractionEventHandlerCaptor; @@ -152,8 +150,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mKeyguardBouncerViewModel, mKeyguardBouncerComponentFactory, mAlternateBouncerInteractor, - mKeyguardTransitionInteractor, - mPrimaryBouncerToGoneTransitionViewModel + mKeyguardTransitionInteractor ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 180d9f8956c32..dc5a0472f49e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -24,8 +24,6 @@ import static com.android.systemui.statusbar.phone.ScrimState.SHADE_LOCKED; import static com.google.common.truth.Truth.assertThat; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -60,12 +58,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.keyguard.shared.model.TransitionState; -import com.android.systemui.keyguard.shared.model.TransitionStep; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -92,10 +85,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import kotlinx.coroutines.CoroutineDispatcher; - @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) +@TestableLooper.RunWithLooper @SmallTest public class ScrimControllerTest extends SysuiTestCase { @@ -124,10 +115,6 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private DockManager mDockManager; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private CoroutineDispatcher mMainDispatcher; - // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The // event-dispatch-on-registration pattern caused some of these unit tests to fail.) @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -238,20 +225,13 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock); when(mDockManager.isDocked()).thenReturn(false); - when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition()) - .thenReturn(emptyFlow()); - when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha()).thenReturn(emptyFlow()); - mScrimController = new ScrimController(mLightBarController, mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), mScreenOffAnimationController, mKeyguardUnlockAnimationController, - mStatusBarKeyguardViewManager, - mPrimaryBouncerToGoneTransitionViewModel, - mKeyguardTransitionInteractor, - mMainDispatcher); + mStatusBarKeyguardViewManager); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -881,10 +861,7 @@ public class ScrimControllerTest extends SysuiTestCase { mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), mScreenOffAnimationController, mKeyguardUnlockAnimationController, - mStatusBarKeyguardViewManager, - mPrimaryBouncerToGoneTransitionViewModel, - mKeyguardTransitionInteractor, - mMainDispatcher); + mStatusBarKeyguardViewManager); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -1652,18 +1629,6 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimAlpha(mScrimBehind, 0); } - @Test - public void ignoreTransitionRequestWhileKeyguardTransitionRunning() { - mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.mPrimaryBouncerToGoneTransition.accept( - new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, - TransitionState.RUNNING, "ScrimControllerTest")); - - // This request should not happen - mScrimController.transitionTo(ScrimState.BOUNCER); - assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED); - } - private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) { mScrimController.setRawPanelExpansionFraction(expansion); finishAnimationsImmediately(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 194ed02712b20..1a371c73550c3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -47,9 +47,6 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow = _isKeyguardShowing - private val _isKeyguardUnlocked = MutableStateFlow(false) - override val isKeyguardUnlocked: Flow = _isKeyguardUnlocked - private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow = _isKeyguardOccluded diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index 16442bb525b68..eac1bd1450333 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -37,7 +37,7 @@ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { _transitions.emit(step) } - override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? { + override fun startTransition(info: TransitionInfo): UUID? { return null } From 76f1203ae838220778bb7efe6749e037fac59efd Mon Sep 17 00:00:00 2001 From: Josh Tsuji Date: Thu, 2 Mar 2023 18:37:06 -0500 Subject: [PATCH 07/74] Explicitly re-show the keyguard if we were hiding. Test: mash the SFPS w/ press to unlock, helps to disable AOD Bug: 266668894 Change-Id: Ie72872b91e97b3456a5cf20db92bc79b1b467676 (cherry picked from commit e4bfb8aa6aa97bad175ec8cd352ecb404e202157) Merged-In: Ie72872b91e97b3456a5cf20db92bc79b1b467676 --- .../keyguard/KeyguardViewMediator.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d914bf5d8d9a0..85554ac9ae44f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1920,20 +1920,24 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // If the keyguard is already showing, see if we don't need to bother re-showing it. Check // flags in both files to account for the hiding animation which results in a delay and - // discrepancy between flags. + // discrepancy between flags. If we're in the middle of hiding, do not short circuit so that + // we explicitly re-set state. if (mShowing && mKeyguardStateController.isShowing()) { - if (mPM.isInteractive()) { + if (mPM.isInteractive() && !mHiding) { // It's already showing, and we're not trying to show it while the screen is off. // We can simply reset all of the views. - if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); + if (DEBUG) Log.d(TAG, "doKeyguard: not showing (instead, resetting) because it is " + + "already showing, we're interactive, and we were not previously hiding. " + + "It should be safe to short-circuit here."); resetStateLocked(); return; } else { - // We are trying to show the keyguard while the screen is off - this results from - // race conditions involving locking while unlocking. Don't short-circuit here and - // ensure the keyguard is fully re-shown. + // We are trying to show the keyguard while the screen is off or while we were in + // the middle of hiding - this results from race conditions involving locking while + // unlocking. Don't short-circuit here and ensure the keyguard is fully re-shown. Log.e(TAG, - "doKeyguard: already showing, but re-showing since we're not interactive"); + "doKeyguard: already showing, but re-showing because we're interactive or " + + "were in the middle of hiding."); } } @@ -2427,11 +2431,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (DEBUG) Log.d(TAG, "handleShow"); } - mHiding = false; mKeyguardExitAnimationRunner = null; mWakeAndUnlocking = false; setPendingLock(false); - setShowingLocked(true); + + // Force if we we're showing in the middle of hiding, to ensure we end up in the correct + // state. + setShowingLocked(true, mHiding /* force */); + if (mHiding) { + Log.d(TAG, "Forcing setShowingLocked because mHiding=true, which means we're " + + "showing in the middle of hiding."); + } + mHiding = false; + mKeyguardViewControllerLazy.get().show(options); resetKeyguardDonePendingLocked(); mHideAnimationRun = false; From 1edefa04f97ab0352bacac919ede135d18137d1f Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Fri, 3 Mar 2023 19:28:16 +0000 Subject: [PATCH 08/74] Notify ScreenOffController of battery saver change It was previously listening for AOD setting changes only, and ingorning when the battery saver was disabling AOD. This caused animations to run (showAodUI) that put statusbarstate into a stuck KEYGUARD state when it should've been SHADE. Fixes: 270910479 Test: atest DozeParametersTest Test: manual, start with unfolded device, turn on batter saver, fold it, and then unlock Change-Id: Iae586f9a2b86f19502f3d66165b633b1c9fc5f7f (cherry picked from commit 10383655a4396253c238a4c2d44b9d4511737629) Merged-In: Iae586f9a2b86f19502f3d66165b633b1c9fc5f7f --- .../com/android/systemui/statusbar/phone/DozeParameters.java | 2 +- .../android/systemui/statusbar/phone/DozeParametersTest.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index b88531e59568f..ae715b3f20c8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -429,7 +429,6 @@ public class DozeParameters implements } dispatchAlwaysOnEvent(); - mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn()); } @Override @@ -469,6 +468,7 @@ public class DozeParameters implements for (Callback callback : mCallbacks) { callback.onAlwaysOnChange(); } + mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn()); } private boolean getPostureSpecificBool( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index eb5edbc21d890..f5b7ca804fbc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -180,6 +180,7 @@ public class DozeParametersTest extends SysuiTestCase { when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + verify(mScreenOffAnimationController).onAlwaysOnChanged(false); assertThat(mDozeParameters.getAlwaysOn()).isFalse(); } @@ -196,13 +197,16 @@ public class DozeParametersTest extends SysuiTestCase { mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true); verify(callback, times(2)).onAlwaysOnChange(); + verify(mScreenOffAnimationController, times(2)).onAlwaysOnChanged(false); assertThat(mDozeParameters.getAlwaysOn()).isFalse(); + reset(mScreenOffAnimationController); reset(callback); when(mBatteryController.isAodPowerSave()).thenReturn(false); mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true); verify(callback).onAlwaysOnChange(); + verify(mScreenOffAnimationController).onAlwaysOnChanged(true); assertThat(mDozeParameters.getAlwaysOn()).isTrue(); } From 654c5afb132475fc0aac0a3c582d8a55e04b46e7 Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Wed, 8 Mar 2023 01:24:46 +0000 Subject: [PATCH 09/74] Revert "Disallow interception of multitouch events" This reverts commit 5336e8103822e98e403e5ae50f146001491d76b1. Reason for revert: b/272146290 Change-Id: I042a076750c5a59bb3fe3e6ff0e0845773d272bf (cherry picked from commit on googleplex-android-review.googlesource.com host: 4eb2fc3078d09b25daa93293e4225e00f9086dce) Merged-In: I042a076750c5a59bb3fe3e6ff0e0845773d272bf --- .../systemui/shade/NotificationPanelViewController.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8323d1e6bae34..3be24173c21bc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4650,13 +4650,8 @@ public final class NotificationPanelViewController implements Dumpable { gesture possible. */ int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { - if (mTrackingPointer < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); - } else { - mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer"); - return false; - } + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); } final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); From ca0cecf9d6b812f0aea1f0881802e7783c6851cb Mon Sep 17 00:00:00 2001 From: Diego Vela Date: Tue, 7 Mar 2023 22:41:53 +0000 Subject: [PATCH 10/74] Revert "Fix vulnerability in AttributionSource due to incorrect ..." Revert submission 21778925-tm-dev-2-attribution-source Reason for revert: b/272115855 Reverted changes: /q/submissionid:21778925-tm-dev-2-attribution-source Bug: 272115855 Change-Id: Ia3d101bab6613da6515acc87af9295da664827cf (cherry picked from commit on googleplex-android-review.googlesource.com host: 5f3a3f2a4bff1344333599f208a9118e22dcde4b) Merged-In: Ia3d101bab6613da6515acc87af9295da664827cf --- .../android/content/AttributionSource.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 16b18c85e7908..3f2fa2188d247 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -31,7 +31,6 @@ import android.os.Parcelable; import android.os.Process; import android.permission.PermissionManager; import android.util.ArraySet; -import android.util.Log; import com.android.internal.annotations.Immutable; @@ -88,8 +87,6 @@ import java.util.Set; */ @Immutable public final class AttributionSource implements Parcelable { - private static final String TAG = "AttributionSource"; - private static final String DESCRIPTOR = "android.content.AttributionSource"; private static final Binder sDefaultToken = new Binder(DESCRIPTOR); @@ -157,20 +154,9 @@ public final class AttributionSource implements Parcelable { AttributionSource(@NonNull Parcel in) { this(AttributionSourceState.CREATOR.createFromParcel(in)); - if (!Binder.isDirectlyHandlingTransaction()) { - Log.e(TAG, "Unable to verify calling UID #" + mAttributionSourceState.uid + " PID #" - + mAttributionSourceState.pid + " when not handling Binder transaction; " - + "clearing."); - mAttributionSourceState.pid = -1; - mAttributionSourceState.uid = -1; - mAttributionSourceState.packageName = null; - mAttributionSourceState.attributionTag = null; - mAttributionSourceState.next = null; - } else { - // Since we just unpacked this object as part of it transiting a Binder - // call, this is the perfect time to enforce that its UID and PID can be trusted - enforceCallingUidAndPid(); - } + // Since we just unpacked this object as part of it transiting a Binder + // call, this is the perfect time to enforce that its UID and PID can be trusted + enforceCallingUidAndPid(); } /** @hide */ From 4771351567469aeedbaac3ba172406309b224888 Mon Sep 17 00:00:00 2001 From: Alison Cichowlas Date: Mon, 13 Mar 2023 20:29:57 +0000 Subject: [PATCH 11/74] Revert "Volume: Hide dialog when motion cancelled, as well as when finished." This reverts commit 3f8dd35901707ee6e0c36b41be413a52b9e1c66a. Reason for revert: b/273157752 (long press flicker) Bug: 273157752, 232606092 Change-Id: Iafd7dce156fea867d16bb3709700e01a19c043b4 (cherry picked from commit on googleplex-android-review.googlesource.com host: 9bdfa459baa940efb2f74e9d612210179ce85263) Merged-In: Iafd7dce156fea867d16bb3709700e01a19c043b4 --- .../systemui/volume/VolumeDialogImpl.java | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 2892ee3c7643e..1cfcf8cbbdcb5 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1398,11 +1398,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void onAnimationCancel(@NonNull Animator animation) { mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL); - Log.i(TAG, "onAnimationCancel"); - - // We can only have one animation listener for cancel, so the jank listener should - // also call for cleanup. - finishDismiss(); + Log.d(TAG, "onAnimationCancel"); } @Override @@ -1494,7 +1490,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .setDuration(mDialogHideAnimationDurationMs) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .withEndAction(() -> mHandler.postDelayed(() -> { - finishDismiss(); + mController.notifyVisible(false); + mDialog.dismiss(); + tryToRemoveCaptionsTooltip(); + mIsAnimatingDismiss = false; + + hideRingerDrawer(); }, 50)); if (!shouldSlideInVolumeTray()) animator.translationX(mDialogView.getWidth() / 2.0f); animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS, @@ -1509,18 +1510,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, Trace.endSection(); } - /** - * Clean up and hide volume dialog. Called when animation is finished/cancelled. - */ - private void finishDismiss() { - mController.notifyVisible(false); - mDialog.dismiss(); - tryToRemoveCaptionsTooltip(); - mIsAnimatingDismiss = false; - - hideRingerDrawer(); - } - private boolean showActiveStreamOnly() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION); From b4e133fb0ec7573c46be0968c5dec695646ec486 Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Fri, 31 Mar 2023 00:09:56 +0000 Subject: [PATCH 12/74] Cancel current animation instead of candidate Otherwise everything along the way confidently ignores this and Keyguard doesn't even see the timeout and force-abort coming when it hits. This leaves an animation timer running for 950ms which sets occlude status on finish callback and causes unpleasant race conditions. Test: atest android.server.wm.KeyguardTests Test: specific case to be added in b/274003805 Bug: 275650364 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8ffedf11e398f9a6aa8b30cff5f06b102fd54536) Merged-In: I7b3dcd06e7483fde745a1d56dfee7c4efc262ed7 Change-Id: I7b3dcd06e7483fde745a1d56dfee7c4efc262ed7 --- .../systemui/keyguard/KeyguardService.java | 18 ++++++++---------- .../keyguard/KeyguardViewMediator.java | 13 +++++++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 4d40db0a0cfd4..f1a8c95469f64 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -260,22 +260,20 @@ public class KeyguardService extends Service { ); } - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) { + public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo, + SurfaceControl.Transaction candidateT, IBinder currentTransition, + IRemoteTransitionFinishedCallback candidateFinishCallback) { try { - final IRemoteTransitionFinishedCallback origFinishCB; + final IRemoteTransitionFinishedCallback currentFinishCB; synchronized (mFinishCallbacks) { - origFinishCB = mFinishCallbacks.remove(transition); + currentFinishCB = mFinishCallbacks.remove(currentTransition); } - info.releaseAllSurfaces(); - t.close(); - if (origFinishCB == null) { - // already finished (or not started yet), so do nothing. + if (currentFinishCB == null) { + Slog.e(TAG, "Called mergeAnimation, but finish callback is missing"); return; } runner.onAnimationCancelled(false /* isKeyguardOccluded */); - origFinishCB.onTransitionFinished(null /* wct */, null /* t */); + currentFinishCB.onTransitionFinished(null /* wct */, null /* t */); } catch (RemoteException e) { // nothing, we'll just let it finish on its own I guess. } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8bd9673d4e871..147961f70bffa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -951,10 +951,15 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onAnimationCancelled(boolean isKeyguardOccluded) { - if (mOccludeByDreamAnimator != null) { - mOccludeByDreamAnimator.cancel(); - } - setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */); + mContext.getMainExecutor().execute(() -> { + if (mOccludeByDreamAnimator != null) { + mOccludeByDreamAnimator.cancel(); + } + }); + // The value of isKeyguardOccluded here may come from mergeAnimation, which + // isn't reliable. In all cases, after running or cancelling this animation, + // keyguard should be occluded. + setOccluded(true /* isOccluded */, false /* animate */); if (DEBUG) { Log.d(TAG, "Occlude by Dream animation cancelled. Occluded state is now: " + mOccluded); From 8c57a52b1ca570133b09f12fcfc063a7fdae28ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ciche=C5=84ski?= Date: Thu, 13 Apr 2023 01:36:20 +0000 Subject: [PATCH 13/74] Revert "Remove pip resize flicks" DO NOT MERGE This reverts commit a0253fb49309d4d249055ff459048a69a188be32. Reason for revert: The original CL introduced regressions on QPR3 http://b/276806110 and http://b/277677631, and the bug it fixed was present in prior releases e.g. TQ2A.230405.002 (9662652). This will be fixed for U, hence the "do not merge" at the top of revert. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ce1ef55f1a4934e16faf5b3ac4ec0b1ea4924324) Merged-In: I4abd2c5b66d837b73f210a85b62ca65bb6aa1f77 Change-Id: I4abd2c5b66d837b73f210a85b62ca65bb6aa1f77 --- .../wm/shell/pip/PipTaskOrganizer.java | 35 +------------------ .../pip/phone/PipResizeGestureHandler.java | 10 +----- .../phone/PipResizeGestureHandlerTest.java | 4 +-- 3 files changed, 4 insertions(+), 45 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c4b5470f461ad..7da750211b7e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -73,7 +73,6 @@ import android.window.TaskOrganizer; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -145,23 +144,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; - // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip - private Runnable mPipFinishResizeWCTRunnable; - - private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback = - new WindowContainerTransactionCallback() { - @Override - public void onTransactionReady(int id, SurfaceControl.Transaction t) { - t.apply(); - - // execute the runnable if non-null after WCT is applied to finish resizing pip - if (mPipFinishResizeWCTRunnable != null) { - mPipFinishResizeWCTRunnable.run(); - mPipFinishResizeWCTRunnable = null; - } - } - }; - // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = new PipAnimationController.PipAnimationCallback() { @@ -1278,23 +1260,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** * Animates resizing of the pinned stack given the duration and start bounds. * This is used when the starting bounds is not the current PiP bounds. - * - * @param pipFinishResizeWCTRunnable callback to run after window updates are complete */ public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, - float startingAngle, Consumer updateBoundsCallback, - Runnable pipFinishResizeWCTRunnable) { - mPipFinishResizeWCTRunnable = pipFinishResizeWCTRunnable; - if (mPipFinishResizeWCTRunnable != null) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "mPipFinishResizeWCTRunnable is set to be called once window updates"); - } - - scheduleAnimateResizePip(fromBounds, toBounds, duration, startingAngle, - updateBoundsCallback); - } - - private void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, float startingAngle, Consumer updateBoundsCallback) { if (mWaitForFixedRotation) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -1599,7 +1566,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSplitScreenOptional.ifPresent(splitScreenController -> splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct)); } else { - mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback); + mTaskOrganizer.applyTransaction(wct); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 3e83c5fcf9a08..41ff0b35a0359 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -580,16 +580,8 @@ public class PipResizeGestureHandler { final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( mLastResizeBounds, movementBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); - - // disable the pinch resizing until the final bounds are updated - final boolean prevEnablePinchResize = mEnablePinchResize; - mEnablePinchResize = false; - mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> { - // reset the pinch resizing to its default state - mEnablePinchResize = prevEnablePinchResize; - }); + PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 5f356c93d0a40..c7b9eb3d10744 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -155,7 +155,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { mPipResizeGestureHandler.onPinchResize(upEvent); verify(mPipTaskOrganizer, times(1)) - .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any()); + .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any()); assertTrue("The new size should be bigger than the original PiP size.", mPipResizeGestureHandler.getLastResizeBounds().width() @@ -194,7 +194,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { mPipResizeGestureHandler.onPinchResize(upEvent); verify(mPipTaskOrganizer, times(1)) - .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any()); + .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any()); assertTrue("The new size should be smaller than the original PiP size.", mPipResizeGestureHandler.getLastResizeBounds().width() From adb11edb1a2bbed69798a67ed3509da6f75d578c Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Wed, 12 Apr 2023 11:51:19 -0400 Subject: [PATCH 14/74] DO NOT MERGE Fix crash related to getting the root of an unattached view view.getRootView returns itself when unattached, which may not be a ViewGroup instance. Therefore trying to cast to a ViewGroup will cause a ClassCastException. This fix avoids the exception by ensuring that the view is attached before trying to cast the root view. Fixes: 277637573 Test: mp droid and verified touch gestures on dream work correctly by swiping up the bouncer, and swiping down the notification shade. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:68cb239bb0e7129d0f415d7a3b77609ab1326a79) Merged-In: Ic2d87278e5c60509c39062437b548ae76959b169 Change-Id: Ic2d87278e5c60509c39062437b548ae76959b169 --- .../src/com/android/systemui/touch/TouchInsetManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java index a4f1ef4fa7560..ef0fa012d56d9 100644 --- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java +++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java @@ -90,6 +90,9 @@ public class TouchInsetManager { final Region cumulativeRegion = Region.obtain(); mTrackedViews.stream().forEach(view -> { + if (!view.isAttachedToWindow()) { + return; + } final Rect boundaries = new Rect(); view.getDrawingRect(boundaries); ((ViewGroup) view.getRootView()).offsetDescendantRectToMyCoords(view, boundaries); From c4a202ae995daffc672ffa90a79e7f903c2c7922 Mon Sep 17 00:00:00 2001 From: William Xiao Date: Wed, 12 Apr 2023 10:17:58 -0700 Subject: [PATCH 15/74] Fix home controls showing on low light dream Low light should never show complications, so short circuit early and don't return any in the state controller. Bug: 277746325 Test: atest DreamOverlayStateControllerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f501b295e11d22455f661707adad9d020b193d25) Merged-In: Iae6388dafa69098b70e9229bc562172fec1fb149 Change-Id: Iae6388dafa69098b70e9229bc562172fec1fb149 --- .../dreams/DreamOverlayStateController.java | 4 ++++ .../dreams/DreamOverlayStateControllerTest.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index a47565390d6b9..b575f9a0ab8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -182,6 +182,10 @@ public class DreamOverlayStateController implements * Returns collection of present {@link Complication}. */ public Collection getComplications(boolean filterByAvailability) { + if (isLowLightActive()) { + // Don't show complications on low light. + return Collections.emptyList(); + } return Collections.unmodifiableCollection(filterByAvailability ? mComplications .stream() diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 34fa76fd5d46e..55f0a8cd3f08f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -234,6 +234,23 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { .isTrue(); } + @Test + public void testComplicationsNotShownForLowLight() { + final Complication complication = Mockito.mock(Complication.class); + final DreamOverlayStateController stateController = getDreamOverlayStateController(true); + + // Add a complication and verify it's returned in getComplications. + stateController.addComplication(complication); + mExecutor.runAllReady(); + assertThat(stateController.getComplications().contains(complication)) + .isTrue(); + + stateController.setLowLightActive(true); + mExecutor.runAllReady(); + + assertThat(stateController.getComplications()).isEmpty(); + } + @Test public void testNotifyLowLightChanged() { final DreamOverlayStateController stateController = getDreamOverlayStateController(true); From 272d3fb6e676510ca2f91b521f160a24d8aff09c Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Fri, 14 Apr 2023 14:54:16 +0000 Subject: [PATCH 16/74] DO NOT MERGE Revert "Accept a Window as shown if it's in any transition" This reverts commit 8ed3e4a826356b5e93008653d310844ea9d4d8e5. Reason for revert: Task embedding flicker b/276472426 Fix: 276472426 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:dd2ba83f7921f070ceee53a795a17c0a8127c266) Merged-In: I81a41081ff5905cba35930f1a0de04c2000b5dfa Change-Id: I81a41081ff5905cba35930f1a0de04c2000b5dfa --- .../core/java/com/android/server/wm/WindowState.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2d21e710bca8e..95fea0ee22f53 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2053,19 +2053,16 @@ class WindowState extends WindowContainer implements WindowManagerP /** * Like isOnScreen(), but we don't return true if the window is part - * of a transition but has not yet started animating. + * of a transition that has not yet been started. */ boolean isReadyForDisplay() { - if (!mHasSurface || mDestroying || !isVisibleByPolicy()) { - return false; - } - if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet() - && !isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) { + if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) { return false; } final boolean parentAndClientVisible = !isParentWindowHidden() && mViewVisibility == View.VISIBLE && mToken.isVisible(); - return parentAndClientVisible || isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_ALL); + return mHasSurface && isVisibleByPolicy() && !mDestroying + && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS)); } boolean isFullyTransparent() { From c34ca49a06cec8abc10236496bfdfbaae9122f96 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Fri, 14 Apr 2023 08:52:46 +0000 Subject: [PATCH 17/74] Make sure to clear outdated screenshot of splitting tasks Make sure to clear the screenshot of splitting tasks if it's taking the new one or being dismissed from split. Bug: 272420717 Bug: 278134600 Test: pass existing tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e4ec07516eb1b824e25d57088963984d2175e5d7) Merged-In: Ia159deccc8f0024143200e4dd44fadf67974a198 Change-Id: Ia159deccc8f0024143200e4dd44fadf67974a198 --- .../android/wm/shell/common/split/SplitDecorManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index bdf0ac2ed30cf..4c2e0dafe698b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -164,6 +164,10 @@ public class SplitDecorManager extends WindowlessWindowManager { t.remove(mGapBackgroundLeash); mGapBackgroundLeash = null; } + if (mScreenshot != null) { + t.remove(mScreenshot); + mScreenshot = null; + } mHostLeash = null; mIcon = null; mResizingIconView = null; @@ -323,6 +327,8 @@ public class SplitDecorManager extends WindowlessWindowManager { if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); + } else if (mScreenshot != null) { + t.remove(mScreenshot); } mTempRect.set(mOldBounds); @@ -339,6 +345,8 @@ public class SplitDecorManager extends WindowlessWindowManager { if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); + } else if (mScreenshot != null) { + t.remove(mScreenshot); } mScreenshot = screenshot; From 41b888f32c83452f1401d6febc0176dacba10819 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Fri, 14 Apr 2023 15:41:03 +0200 Subject: [PATCH 18/74] AudioService: fix volume group setting name The setting name for a volume group must also be updated when the corresponding public stream setting name is changed. Also no need to persist the volume group index when there is a corresponding public stream as this is redundant. Bug: 270815307 Test: repro steps in bug Test: ates AudioManagerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:205fdba74eeced1b9f9cb13b415afa6e87ea72f8) Merged-In: I4edc8036201cba1fb7f79f12f434a871f6010114 Change-Id: I4edc8036201cba1fb7f79f12f434a871f6010114 --- .../android/server/audio/AudioService.java | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 158492056fb5a..3ddb0ae96a227 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2251,8 +2251,8 @@ public class AudioService extends IAudioService.Stub synchronized (VolumeStreamState.class) { mStreamStates[AudioSystem.STREAM_DTMF] .setAllIndexes(mStreamStates[dtmfStreamAlias], caller); - mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].mVolumeIndexSettingName = - System.VOLUME_SETTINGS_INT[a11yStreamAlias]; + mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setSettingName( + System.VOLUME_SETTINGS_INT[a11yStreamAlias]); mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes( mStreamStates[a11yStreamAlias], caller); } @@ -7505,7 +7505,7 @@ public class AudioService extends IAudioService.Stub private int mPublicStreamType = AudioSystem.STREAM_MUSIC; private AudioAttributes mAudioAttributes = AudioProductStrategy.getDefaultAttributes(); private boolean mIsMuted = false; - private final String mSettingName; + private String mSettingName; // No API in AudioSystem to get a device from strategy or from attributes. // Need a valid public stream type to use current API getDeviceForStream @@ -7834,15 +7834,19 @@ public class AudioService extends IAudioService.Stub } private void persistVolumeGroup(int device) { - if (mUseFixedVolume) { + // No need to persist the index if the volume group is backed up + // by a public stream type as this is redundant + if (mUseFixedVolume || mHasValidStreamType) { return; } if (DEBUG_VOL) { Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group " + mAudioVolumeGroup.name() + ", device " + AudioSystem.getOutputDeviceName(device) - + " and User=" + getCurrentUserId()); + + " and User=" + getCurrentUserId() + + " mSettingName: " + mSettingName); } + boolean success = mSettings.putSystemIntForUser(mContentResolver, getSettingNameForDevice(device), getIndex(device), @@ -7905,6 +7909,14 @@ public class AudioService extends IAudioService.Stub return mSettingName + "_" + AudioSystem.getOutputDeviceName(device); } + void setSettingName(String settingName) { + mSettingName = settingName; + } + + String getSettingName() { + return mSettingName; + } + private void dump(PrintWriter pw) { pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":"); pw.print(" Muted: "); @@ -8029,6 +8041,9 @@ public class AudioService extends IAudioService.Stub */ public void setVolumeGroupState(VolumeGroupState volumeGroupState) { mVolumeGroupState = volumeGroupState; + if (mVolumeGroupState != null) { + mVolumeGroupState.setSettingName(mVolumeIndexSettingName); + } } /** * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission @@ -8099,6 +8114,17 @@ public class AudioService extends IAudioService.Stub return (mVolumeIndexSettingName != null && !mVolumeIndexSettingName.isEmpty()); } + void setSettingName(String settingName) { + mVolumeIndexSettingName = settingName; + if (mVolumeGroupState != null) { + mVolumeGroupState.setSettingName(mVolumeIndexSettingName); + } + } + + String getSettingName() { + return mVolumeIndexSettingName; + } + public void readSettings() { synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { @@ -8747,7 +8773,7 @@ public class AudioService extends IAudioService.Stub if (streamState.hasValidSettingsName()) { mSettings.putSystemIntForUser(mContentResolver, streamState.getSettingNameForDevice(device), - (streamState.getIndex(device) + 5)/ 10, + (streamState.getIndex(device) + 5) / 10, UserHandle.USER_CURRENT); } } From 6c52d0e7bbd34e1b3eb499c8cdca6e608343d81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Kurucz?= Date: Fri, 14 Apr 2023 16:49:31 +0000 Subject: [PATCH 19/74] Reset NSSL translationY after a transision gets cancelled We update the translationY of the NSSL upon multiple transitions. If the Occluded -> LockScreen transition is cancelled, there is no other transition that is taking over. It causes the NSSL to get stuck with some negative translateY value. This caused the Notifications and the NotificationShelf to be shifted slightly upwards. This fix resets the translation to 0 if the transition was interrupted and not completed successfully. Fixes: 273591201 Test: atest OccludedToLockscreenTransitionViewModelTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3a23e91c1a76f22ac0da52e942e405525c539b2a) Merged-In: If0c162a7f1b8a2cf3d261692f5ce333331c36a7b Change-Id: If0c162a7f1b8a2cf3d261692f5ce333331c36a7b --- .../OccludedToLockscreenTransitionViewModel.kt | 1 + ...OccludedToLockscreenTransitionViewModelTest.kt | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 5770f3ee8876f..ddce516a0fb22 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -47,6 +47,7 @@ constructor( duration = TO_LOCKSCREEN_DURATION, onStep = { value -> -translatePx + value * translatePx }, interpolator = EMPHASIZED_DECELERATE, + onCancel = { 0f }, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 0c4e84521a36d..efa5f0c966e3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -92,6 +92,21 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun lockscreenTranslationYResettedAfterJobCancelled() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf() + + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED)) + + assertThat(values.last()).isEqualTo(0f) + + job.cancel() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING From 39cd9be33d2dc555b552565fed8a15541b14a4f0 Mon Sep 17 00:00:00 2001 From: Graciela Wissen Putri Date: Tue, 11 Apr 2023 16:38:32 +0000 Subject: [PATCH 20/74] Rename orientation request loop property Rename property from PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED to PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED. Bug: 277269510 Test: atest WmTests:LetterboxUiControllerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:d96dee092dc85c4978e1d8fef2eb9a96e2720c15) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e63c8093ecbc2195d81cf4c42f71e5ca86bb8e22) Merged-In: Ib73d0fc254cef90fee694cd6c5c00c0eb85a3ca1 Change-Id: Ib73d0fc254cef90fee694cd6c5c00c0eb85a3ca1 --- core/java/android/view/WindowManager.java | 6 +++--- .../com/android/server/wm/LetterboxUiController.java | 10 +++++----- .../android/server/wm/LetterboxUiControllerTest.java | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 033f7263bfc6e..d67a476a428dc 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -877,7 +877,7 @@ public interface WindowManager extends ViewManager { * <application> * <property * android:name= - * "android.window.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED" + * "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED" * android:value="false"/> * </application> * @@ -885,8 +885,8 @@ public interface WindowManager extends ViewManager { * @hide */ // TODO(b/274924641): Make this public API. - String PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = - "android.window.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED"; + String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = + "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED"; /** * Application level {@link android.content.pm.PackageManager.Property PackageManager diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 4b2cb7db56cbf..684e787ec4cff 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -48,9 +48,9 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTA import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; -import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; @@ -241,7 +241,7 @@ final class LetterboxUiController { private final Boolean mBooleanPropertyIgnoreRequestedOrientation; @Nullable - private final Boolean mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected; + private final Boolean mBooleanPropertyAllowIgnoringOrientationRequestWhenLoopDetected; @Nullable private final Boolean mBooleanPropertyFakeFocus; @@ -264,10 +264,10 @@ final class LetterboxUiController { readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); - mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected = + mBooleanPropertyAllowIgnoringOrientationRequestWhenLoopDetected = readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, - PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); + PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); mBooleanPropertyFakeFocus = readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isCompatFakeFocusEnabled, @@ -453,7 +453,7 @@ final class LetterboxUiController { /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled, - mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected)) { + mBooleanPropertyAllowIgnoringOrientationRequestWhenLoopDetected)) { return false; } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index b277283134e28..4ffd18a99d58b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -39,9 +39,9 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTA import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; -import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -206,7 +206,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { throws Exception { doReturn(true).when(mLetterboxConfiguration) .isPolicyForIgnoringRequestedOrientationEnabled(); - mockThatProperty(PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, + mockThatProperty(PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, /* value */ false); doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); From 6005e82bc73f6369fbd3cadb778296345dd1e77f Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Wed, 29 Mar 2023 14:02:51 -0700 Subject: [PATCH 21/74] Fix KeyguardSecurityContainerControllerTest Fixing tests that are failing for other build targets. For orientation, I think the orientation in resources is already defined as landscape, which is causing the failure in the test. For the gravity, we have different overlays based on screen size so we can be so specific with gravity. Test: Run on ABTD Fixes: 268367452 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8eb68c7ebef719a1459e01c1624a82f98991ca57) Merged-In: Ie341c5d85845098b358f247e6eb8fad05ff0029c Change-Id: Ie341c5d85845098b358f247e6eb8fad05ff0029c --- ...yguardSecurityContainerControllerTest.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index d492758627577..3b3df239b8ea7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -163,8 +163,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor mSwipeListenerArgumentCaptor; - private Configuration mConfiguration; - private KeyguardSecurityContainerController mKeyguardSecurityContainerController; private KeyguardPasswordViewController mKeyguardPasswordViewController; private KeyguardPasswordView mKeyguardPasswordView; @@ -172,12 +170,12 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Before public void setup() { - mConfiguration = new Configuration(); - mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED. mTestableResources = mContext.getOrCreateTestableResources(); + mTestableResources.getResources().getConfiguration().orientation = + Configuration.ORIENTATION_UNDEFINED; when(mView.getContext()).thenReturn(mContext); - when(mView.getResources()).thenReturn(mContext.getResources()); + when(mView.getResources()).thenReturn(mTestableResources.getResources()); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0); lp.gravity = 0; @@ -254,6 +252,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void onResourcesUpdate_callsThroughOnRotationChange() { + clearInvocations(mView); + // Rotation is the same, shouldn't cause an update mKeyguardSecurityContainerController.updateResources(); verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), @@ -579,12 +579,12 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { // Set initial gravity mTestableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER); + mTestableResources.addOverride( + R.bool.can_use_one_handed_bouncer, false); // Kick off the initial pass... mKeyguardSecurityContainerController.onInit(); - verify(mView).setLayoutParams(argThat( - (ArgumentMatcher) argument -> - argument.gravity == Gravity.CENTER)); + verify(mView).setLayoutParams(any()); clearInvocations(mView); // Now simulate a config change @@ -592,9 +592,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); mKeyguardSecurityContainerController.updateResources(); - verify(mView).setLayoutParams(argThat( - (ArgumentMatcher) argument -> - argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM))); + verify(mView).setLayoutParams(any()); } @Test From b5d889f5fd0278eaf8f41949e68e50be5af0929f Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Wed, 19 Apr 2023 14:19:54 +0200 Subject: [PATCH 22/74] Restrict maximum size of FontInterpolator font caches This fixes a memory leak where Fonts were leaked within detailed lock screen animations. We're occasionally animating fonts via several axes which causes a low occurence of cache hits. This resulted in hundreds of Font instances being kept in the cache. Bug: 275486055 Test: Verified behaviour on cheetah Expanded and ran unit tests for affected classes Benchmarked rendering time with reduced cache - increase of duration of draw() command for 0.3ms with 10 item cache. 50 item cache increased draw() command duration for 0.15ms on average which is not worth the tradeoff in memory. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b7bc5640491d0a4cc0fd6c02f73c82912b1e3996) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:39dbe90004924912f49e141b2051bc27752f7c1a) Merged-In: Id6e2b127f44f5e553ff7ac4ba640fd4f6f4698b0 Change-Id: Id6e2b127f44f5e553ff7ac4ba640fd4f6f4698b0 --- .../systemui/animation/FontInterpolator.kt | 16 ++++++++---- .../systemui/animation/TextAnimator.kt | 12 +++++---- .../animation/FontInterpolatorTest.kt | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index f0a82113c3a3b..83e44b69812b2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt @@ -18,8 +18,10 @@ package com.android.systemui.animation import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis +import android.util.LruCache import android.util.MathUtils import android.util.MathUtils.abs +import androidx.annotation.VisibleForTesting import java.lang.Float.max import java.lang.Float.min @@ -34,6 +36,10 @@ private const val FONT_ITALIC_MIN = 0f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_DEFAULT_VALUE = 0f +// Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in +// frame draw time on a Pixel 6. +@VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10 + /** Provide interpolation of two fonts by adjusting font variation settings. */ class FontInterpolator { @@ -81,8 +87,8 @@ class FontInterpolator { // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. - private val interpCache = hashMapOf() - private val verFontCache = hashMapOf() + private val interpCache = LruCache(FONT_CACHE_MAX_ENTRIES) + private val verFontCache = LruCache(FONT_CACHE_MAX_ENTRIES) // Mutable keys for recycling. private val tmpInterpKey = InterpKey(null, null, 0f) @@ -152,7 +158,7 @@ class FontInterpolator { tmpVarFontKey.set(start, newAxes) val axesCachedFont = verFontCache[tmpVarFontKey] if (axesCachedFont != null) { - interpCache[InterpKey(start, end, progress)] = axesCachedFont + interpCache.put(InterpKey(start, end, progress), axesCachedFont) return axesCachedFont } @@ -160,8 +166,8 @@ class FontInterpolator { // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // not do any IO work. val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() - interpCache[InterpKey(start, end, progress)] = newFont - verFontCache[VarFontKey(start, newAxes)] = newFont + interpCache.put(InterpKey(start, end, progress), newFont) + verFontCache.put(VarFontKey(start, newAxes), newFont) return newFont } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 9e9929e79d47d..3ee97be360f0b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -24,8 +24,10 @@ import android.graphics.Canvas import android.graphics.Typeface import android.graphics.fonts.Font import android.text.Layout +import android.util.LruCache private const val DEFAULT_ANIMATION_DURATION: Long = 300 +private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** @@ -114,7 +116,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { private val fontVariationUtils = FontVariationUtils() - private val typefaceCache = HashMap() + private val typefaceCache = LruCache(TYPEFACE_CACHE_MAX_ENTRIES) fun updateLayout(layout: Layout) { textInterpolator.layout = layout @@ -218,12 +220,12 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { } if (!fvar.isNullOrBlank()) { - textInterpolator.targetPaint.typeface = - typefaceCache.getOrElse(fvar) { - textInterpolator.targetPaint.fontVariationSettings = fvar + textInterpolator.targetPaint.typeface = typefaceCache.get(fvar) ?: run { + textInterpolator.targetPaint.fontVariationSettings = fvar + textInterpolator.targetPaint.typeface?.also { typefaceCache.put(fvar, textInterpolator.targetPaint.typeface) - textInterpolator.targetPaint.typeface } + } } if (color != null) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt index 8a5c5b58d0580..57a355f4e1278 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt @@ -106,4 +106,29 @@ class FontInterpolatorTest : SysuiTestCase() { val reversedFont = interp.lerp(endFont, startFont, 0.5f) assertThat(resultFont).isSameInstanceAs(reversedFont) } + + @Test + fun testCacheMaxSize() { + val interp = FontInterpolator() + + val startFont = Font.Builder(sFont) + .setFontVariationSettings("'wght' 100") + .build() + val endFont = Font.Builder(sFont) + .setFontVariationSettings("'wght' 1") + .build() + val resultFont = interp.lerp(startFont, endFont, 0.5f) + for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) { + val f1 = Font.Builder(sFont) + .setFontVariationSettings("'wght' ${i * 100}") + .build() + val f2 = Font.Builder(sFont) + .setFontVariationSettings("'wght' $i") + .build() + interp.lerp(f1, f2, 0.5f) + } + + val cachedFont = interp.lerp(startFont, endFont, 0.5f) + assertThat(resultFont).isNotSameInstanceAs(cachedFont) + } } From dfd75611b414c02f18a8e67a403661871afb3ef1 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 20 Apr 2023 22:50:33 +0000 Subject: [PATCH 23/74] Update the disabled-opa navbar icon size to reflect icon change - The sizes of the button icons were shrunked, but opa layout (for the home button) scales the icon to another specified size which also needs to be updated Fixes: 278011905 Test: go/nav-bar-icon-screenshot (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1709864c4883f54397afb0f4b819b5a90c9e2055) Merged-In: Ieca803c3739adfa557ae8a5d20d28d9dd8511930 Change-Id: Ieca803c3739adfa557ae8a5d20d28d9dd8511930 --- packages/SystemUI/res/values/dimens.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3faacf2882ad6..adc4c555dfacf 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -396,7 +396,7 @@ 70dp - 32dp + 24dp From 8b6d722b7536d3a99916118bb6ed1fb8df35fd8d Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Mon, 17 Apr 2023 15:05:23 -0700 Subject: [PATCH 24/74] Hide EmptyShadeView when on bouncer Added a check to make sure EmptyShadeView is hidden when bouncer is showing. Bug: 267060171 Test: manually verified EmptyShadeView visibility (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bef2be6757eec92391189c106e89b63176cd16c0) Merged-In: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 Change-Id: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 --- .../systemui/shade/NotificationPanelViewController.java | 1 + .../stack/NotificationStackScrollLayoutController.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7388c27ed3131..6cd0aee62551f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2843,6 +2843,7 @@ public final class NotificationPanelViewController implements Dumpable { /** Set whether the bouncer is showing. */ public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 906b9592e3ddd..f6c260a875ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1240,7 +1240,8 @@ public class NotificationStackScrollLayoutController { // Hide empty shade view when in transition to Keyguard. // That avoids "No Notifications" to blink when transitioning to AOD. // For more details, see: b/228790482 - && !isInTransitionToKeyguard(); + && !isInTransitionToKeyguard() + && !mCentralSurfaces.isBouncerShowing(); mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); From e210bd3a3fccdccb35a1201c91fc491bf9cf4952 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Tue, 18 Apr 2023 14:39:39 -0700 Subject: [PATCH 25/74] Adding tests for updated EmptyShadeView logic Tests for ag/22688474 Bug: 267060171 Test: tests run (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6a19675b120b497618259cbcdedde4ffb68389c8) Merged-In: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 Change-Id: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 --- ...cationStackScrollLayoutControllerTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 45ae96c10345c..fbfe51a897844 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -294,6 +294,34 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { /* notifVisibleInShade= */ false); } + @Test + public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ false, + /* areNotificationsHiddenInShade= */ false); + } + + @Test + public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ true, + /* areNotificationsHiddenInShade= */ false); + } + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); From 1f78af8e964664dfd1e3bdbc9c36599ea0ed1fa3 Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Wed, 19 Apr 2023 06:37:09 +0000 Subject: [PATCH 26/74] Prevent force showing system bars for TaskView Update the condition of force showing system bars to check adjacent tasks instead of checking multi-window windowing mode so it can distinguish from TaskView. Bug: 273495037 Test: atest TaskDisplayAreaTests DisplayPolicyTests InsetsPolicyTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2bd0fe76a6b4ef566386e1949e4db0cf0db16430) Merged-In: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 Change-Id: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 --- .../com/android/server/wm/DisplayPolicy.java | 9 ++++----- .../core/java/com/android/server/wm/Task.java | 16 ++++++++++++++++ .../com/android/server/wm/InsetsPolicyTest.java | 8 +++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 988e98f28d107..0da178b2bcd0a 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -2382,16 +2381,16 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final boolean multiWindowTaskVisible = + final boolean adjacentTasksVisible = defaultTaskDisplayArea.getRootTask(task -> task.isVisible() - && task.getTopLeafTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) + && task.getAdjacentTask() != null) != null; final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); // We need to force showing system bars when the multi-window or freeform root task is // visible. - mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; // We need to force the consumption of the system bars if they are force shown or if they // are controlled by a remote insets controller. mForceConsumeSystemBars = mForceShowSystemBars @@ -2412,7 +2411,7 @@ public class DisplayPolicy { int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS; appearance = configureStatusBarOpacity(appearance); - appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, + appearance = configureNavBarOpacity(appearance, adjacentTasksVisible, freeformRootTaskVisible); final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f5c44d94160b8..1ddc3e8c19b3d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2385,6 +2385,22 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } + /** @return the first adjacent task of this task or its parent. */ + @Nullable + Task getAdjacentTask() { + final TaskFragment adjacentTaskFragment = getAdjacentTaskFragment(); + if (adjacentTaskFragment != null && adjacentTaskFragment.asTask() != null) { + return adjacentTaskFragment.asTask(); + } + + final WindowContainer parent = getParent(); + if (parent == null || parent.asTask() == null) { + return null; + } + + return parent.asTask().getAdjacentTask(); + } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. boolean isRootTask() { return getRootTask() == this; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index d3282b97a6b82..9f376ad6edaba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -81,12 +81,14 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_multiWindowTaskVisible() { + public void testControlsForDispatch_adjacentTasksVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); + final Task task1 = createTask(mDisplayContent); + final Task task2 = createTask(mDisplayContent); + task1.setAdjacentTaskFragment(task2); + final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); // The app must not control any system bars. From aa95a94565813973ea7d63a8826e0bc1b358758c Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Mon, 17 Apr 2023 15:05:23 -0700 Subject: [PATCH 27/74] Hide EmptyShadeView when on bouncer Added a check to make sure EmptyShadeView is hidden when bouncer is showing. Bug: 267060171 Test: manually verified EmptyShadeView visibility (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bef2be6757eec92391189c106e89b63176cd16c0) Merged-In: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 Change-Id: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 --- .../systemui/shade/NotificationPanelViewController.java | 1 + .../stack/NotificationStackScrollLayoutController.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7388c27ed3131..6cd0aee62551f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2843,6 +2843,7 @@ public final class NotificationPanelViewController implements Dumpable { /** Set whether the bouncer is showing. */ public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 906b9592e3ddd..f6c260a875ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1240,7 +1240,8 @@ public class NotificationStackScrollLayoutController { // Hide empty shade view when in transition to Keyguard. // That avoids "No Notifications" to blink when transitioning to AOD. // For more details, see: b/228790482 - && !isInTransitionToKeyguard(); + && !isInTransitionToKeyguard() + && !mCentralSurfaces.isBouncerShowing(); mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); From 6be2b4787b30f5091330bf0d6415a1ea84105516 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Tue, 18 Apr 2023 14:39:39 -0700 Subject: [PATCH 28/74] Adding tests for updated EmptyShadeView logic Tests for ag/22688474 Bug: 267060171 Test: tests run (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6a19675b120b497618259cbcdedde4ffb68389c8) Merged-In: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 Change-Id: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 --- ...cationStackScrollLayoutControllerTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 45ae96c10345c..fbfe51a897844 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -294,6 +294,34 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { /* notifVisibleInShade= */ false); } + @Test + public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ false, + /* areNotificationsHiddenInShade= */ false); + } + + @Test + public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + initController(/* viewIsAttached= */ true); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ true, + /* areNotificationsHiddenInShade= */ false); + } + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); From 2023ade1da2f5371fb7e062d2592c853280fc8af Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Wed, 19 Apr 2023 06:37:09 +0000 Subject: [PATCH 29/74] Prevent force showing system bars for TaskView Update the condition of force showing system bars to check adjacent tasks instead of checking multi-window windowing mode so it can distinguish from TaskView. Bug: 273495037 Test: atest TaskDisplayAreaTests DisplayPolicyTests InsetsPolicyTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2bd0fe76a6b4ef566386e1949e4db0cf0db16430) Merged-In: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 Change-Id: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 --- .../com/android/server/wm/DisplayPolicy.java | 9 ++++----- .../core/java/com/android/server/wm/Task.java | 16 ++++++++++++++++ .../com/android/server/wm/InsetsPolicyTest.java | 8 +++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 988e98f28d107..0da178b2bcd0a 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -2382,16 +2381,16 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final boolean multiWindowTaskVisible = + final boolean adjacentTasksVisible = defaultTaskDisplayArea.getRootTask(task -> task.isVisible() - && task.getTopLeafTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) + && task.getAdjacentTask() != null) != null; final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); // We need to force showing system bars when the multi-window or freeform root task is // visible. - mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; // We need to force the consumption of the system bars if they are force shown or if they // are controlled by a remote insets controller. mForceConsumeSystemBars = mForceShowSystemBars @@ -2412,7 +2411,7 @@ public class DisplayPolicy { int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS; appearance = configureStatusBarOpacity(appearance); - appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, + appearance = configureNavBarOpacity(appearance, adjacentTasksVisible, freeformRootTaskVisible); final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f5c44d94160b8..1ddc3e8c19b3d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2385,6 +2385,22 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } + /** @return the first adjacent task of this task or its parent. */ + @Nullable + Task getAdjacentTask() { + final TaskFragment adjacentTaskFragment = getAdjacentTaskFragment(); + if (adjacentTaskFragment != null && adjacentTaskFragment.asTask() != null) { + return adjacentTaskFragment.asTask(); + } + + final WindowContainer parent = getParent(); + if (parent == null || parent.asTask() == null) { + return null; + } + + return parent.asTask().getAdjacentTask(); + } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. boolean isRootTask() { return getRootTask() == this; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index d3282b97a6b82..9f376ad6edaba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -81,12 +81,14 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_multiWindowTaskVisible() { + public void testControlsForDispatch_adjacentTasksVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); + final Task task1 = createTask(mDisplayContent); + final Task task2 = createTask(mDisplayContent); + task1.setAdjacentTaskFragment(task2); + final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); // The app must not control any system bars. From 1dd424018a2f72446a405c86f2e39e521572bffe Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Fri, 21 Apr 2023 14:54:13 -0400 Subject: [PATCH 30/74] Don't show home controls complication if not available. There is a toggle to disable home controls in Settings, and we need to honor that toggle when determining if the complication should be shown on the dream. Bug: 278964460 Test: atest DreamOverlayStateControllerTest Test: flashed device and disabled toggle in settings, verified home controls does not show on clock dream (cherry picked from commit 2955b163175b3dbdf69df34ea0c6f8c8c40642d3) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f7cf03045f46a10d1cdb0f0c0134cb06ad2c44e7) Merged-In: I4e72f4619fd0ebbf5a78620796f0114ceabc9365 Change-Id: I4e72f4619fd0ebbf5a78620796f0114ceabc9365 --- .../dreams/DreamOverlayStateController.java | 3 +- .../DreamOverlayStateControllerTest.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index b575f9a0ab8b0..a2872e3c639b3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -197,7 +197,8 @@ public class DreamOverlayStateController implements if (mShouldShowComplications) { return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes; } - return (requiredTypes & mSupportedTypes) == requiredTypes; + final int typesToAlwaysShow = mSupportedTypes & getAvailableComplicationTypes(); + return (requiredTypes & typesToAlwaysShow) == requiredTypes; }) .collect(Collectors.toCollection(HashSet::new)) : mComplications); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 55f0a8cd3f08f..d0c1c4db62d43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -353,6 +353,34 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { } } + @Test + public void testHomeControlsDoNotShowIfNotAvailable_featureEnabled() { + when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true); + + final DreamOverlayStateController stateController = getDreamOverlayStateController(true); + stateController.setShouldShowComplications(true); + + final Complication homeControlsComplication = Mockito.mock(Complication.class); + when(homeControlsComplication.getRequiredTypeAvailability()) + .thenReturn(Complication.COMPLICATION_TYPE_HOME_CONTROLS); + + stateController.addComplication(homeControlsComplication); + + final DreamOverlayStateController.Callback callback = + Mockito.mock(DreamOverlayStateController.Callback.class); + + stateController.addCallback(callback); + mExecutor.runAllReady(); + + // No home controls since it is not available. + assertThat(stateController.getComplications()).doesNotContain(homeControlsComplication); + + stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_HOME_CONTROLS + | Complication.COMPLICATION_TYPE_WEATHER); + mExecutor.runAllReady(); + assertThat(stateController.getComplications()).contains(homeControlsComplication); + } + private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) { return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags); } From 9ee1c6ba6f3c2ba1a226abc622e86662b692b605 Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Wed, 19 Apr 2023 06:37:09 +0000 Subject: [PATCH 31/74] Prevent force showing system bars for TaskView Update the condition of force showing system bars to check adjacent tasks instead of checking multi-window windowing mode so it can distinguish from TaskView. Bug: 273495037 Test: atest TaskDisplayAreaTests DisplayPolicyTests InsetsPolicyTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2bd0fe76a6b4ef566386e1949e4db0cf0db16430) Merged-In: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 Change-Id: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 --- .../com/android/server/wm/DisplayPolicy.java | 9 ++++----- .../core/java/com/android/server/wm/Task.java | 16 ++++++++++++++++ .../com/android/server/wm/InsetsPolicyTest.java | 8 +++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 988e98f28d107..0da178b2bcd0a 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -2382,16 +2381,16 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final boolean multiWindowTaskVisible = + final boolean adjacentTasksVisible = defaultTaskDisplayArea.getRootTask(task -> task.isVisible() - && task.getTopLeafTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) + && task.getAdjacentTask() != null) != null; final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); // We need to force showing system bars when the multi-window or freeform root task is // visible. - mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; // We need to force the consumption of the system bars if they are force shown or if they // are controlled by a remote insets controller. mForceConsumeSystemBars = mForceShowSystemBars @@ -2412,7 +2411,7 @@ public class DisplayPolicy { int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS; appearance = configureStatusBarOpacity(appearance); - appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, + appearance = configureNavBarOpacity(appearance, adjacentTasksVisible, freeformRootTaskVisible); final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f5c44d94160b8..1ddc3e8c19b3d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2385,6 +2385,22 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } + /** @return the first adjacent task of this task or its parent. */ + @Nullable + Task getAdjacentTask() { + final TaskFragment adjacentTaskFragment = getAdjacentTaskFragment(); + if (adjacentTaskFragment != null && adjacentTaskFragment.asTask() != null) { + return adjacentTaskFragment.asTask(); + } + + final WindowContainer parent = getParent(); + if (parent == null || parent.asTask() == null) { + return null; + } + + return parent.asTask().getAdjacentTask(); + } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. boolean isRootTask() { return getRootTask() == this; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index d3282b97a6b82..9f376ad6edaba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -81,12 +81,14 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_multiWindowTaskVisible() { + public void testControlsForDispatch_adjacentTasksVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); + final Task task1 = createTask(mDisplayContent); + final Task task2 = createTask(mDisplayContent); + task1.setAdjacentTaskFragment(task2); + final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); // The app must not control any system bars. From 6d7cf18bb93586ff7198e500689f6f5592f2439c Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Tue, 18 Apr 2023 14:39:39 -0700 Subject: [PATCH 32/74] DO NOT MERGE Adding tests for updated EmptyShadeView logic Tests for ag/22688474 Bug: 267060171 Test: tests run (cherry picked from commit 6a19675b120b497618259cbcdedde4ffb68389c8) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e6fe1a0ef1ebb9123f4ed943c8470a873efbaada) Merged-In: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 Change-Id: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 --- ...cationStackScrollLayoutControllerTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 45ae96c10345c..ac60626ba768c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -294,6 +294,34 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { /* notifVisibleInShade= */ false); } + @Test + public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + mController.attach(mNotificationStackScrollLayout); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ false, + /* areNotificationsHiddenInShade= */ false); + } + + @Test + public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + mController.attach(mNotificationStackScrollLayout); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ true, + /* areNotificationsHiddenInShade= */ false); + } + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); From 301078629d95fc3e61aa51cf9be047632491a203 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Mon, 17 Apr 2023 15:05:23 -0700 Subject: [PATCH 33/74] DO NOT MERGE Hide EmptyShadeView when on bouncer Added a check to make sure EmptyShadeView is hidden when bouncer is showing. Bug: 267060171 Test: manually verified EmptyShadeView visibility (cherry picked from commit bef2be6757eec92391189c106e89b63176cd16c0) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2e640447d16c4e893927e2f76bda8b1073bb18f8) Merged-In: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 Change-Id: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 --- .../systemui/shade/NotificationPanelViewController.java | 1 + .../stack/NotificationStackScrollLayoutController.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7388c27ed3131..6cd0aee62551f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2843,6 +2843,7 @@ public final class NotificationPanelViewController implements Dumpable { /** Set whether the bouncer is showing. */ public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 906b9592e3ddd..f6c260a875ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1240,7 +1240,8 @@ public class NotificationStackScrollLayoutController { // Hide empty shade view when in transition to Keyguard. // That avoids "No Notifications" to blink when transitioning to AOD. // For more details, see: b/228790482 - && !isInTransitionToKeyguard(); + && !isInTransitionToKeyguard() + && !mCentralSurfaces.isBouncerShowing(); mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); From b917659324aea87380dfbe51d429fe65aae9ffad Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Fri, 5 May 2023 10:24:40 +0000 Subject: [PATCH 34/74] Unfreeze the surface when the transition is done The surface was not unfreeze if the WindowContainer is closing and changing after [1]. Always check and unfreeze the surface if needed when the transition is done. [1] d19ad2aa09fd27ec11e626ab239babb929c32fd6 Bug: 280754787 Test: click web link on chat while having a Meet call (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f3a5c5892402916ca861c41319f21acb83bd1f83) Merged-In: I98618477a828eb72b2173af6988e804471139e81 Change-Id: I98618477a828eb72b2173af6988e804471139e81 --- services/core/java/com/android/server/wm/WindowContainer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 02d3af6da326e..6ec08dc08c517 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1396,6 +1396,9 @@ class WindowContainer extends ConfigurationContainer< } void onAppTransitionDone() { + if (mSurfaceFreezer.hasLeash()) { + mSurfaceFreezer.unfreeze(getSyncTransaction()); + } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onAppTransitionDone(); From e8e2f0b91345ca5a13ec15fdaa78f77aa96f96c4 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Tue, 7 Mar 2023 10:35:42 +0000 Subject: [PATCH 35/74] Skip letterboxing if the activity below is embedded Embedded activities are resize-able and won't be letterboxed Bug: 270681405 fixes: 280898266 Test: SizeCompatTests Test: manual - repro step in bug (cherry picked from commit b828087bbc1bfead31a9a4e2f54b162d04a10172) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ad7d89c22bf53fbcfd1f8d5a1ae2c611c376e9b3) Merged-In: Ic7b1c4b40960fd04de9efbf4f6d7abee45c93025 Change-Id: Ic7b1c4b40960fd04de9efbf4f6d7abee45c93025 --- .../server/wm/LetterboxUiController.java | 4 ++-- .../android/server/wm/SizeCompatTests.java | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 684e787ec4cff..86bc6eeecf1f1 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1595,9 +1595,9 @@ final class LetterboxUiController { FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null) { + if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any opaque - // activities beneath + // activities beneath or the activity below is embedded which never has letterbox. return; } inheritConfiguration(firstOpaqueActivityBeneath); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index d93871e59b37f..e5ad01a5b70c8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -475,7 +475,25 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testTranslucentActivitiesDontGoInSizeCompatMode() { + public void testNotApplyStrategyToTranslucentActivitiesOverEmbeddedActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Mock the activity as embedded without additional TaskFragment layer in the task for + // simplicity. + doReturn(true).when(mActivity).isEmbedded(); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build(); + doReturn(false).when(translucentActivity).matchParentBounds(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Check the strategy has not being applied + assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + } + + @Test + public void testTranslucentActivitiesDontGoInSizeCompactMode() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); From a01413ad3af92a263c9c17c929521a3067a3f10b Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Wed, 19 Apr 2023 06:37:09 +0000 Subject: [PATCH 36/74] Prevent force showing system bars for TaskView Update the condition of force showing system bars to check adjacent tasks instead of checking multi-window windowing mode so it can distinguish from TaskView. Bug: 273495037 Test: atest TaskDisplayAreaTests DisplayPolicyTests InsetsPolicyTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2bd0fe76a6b4ef566386e1949e4db0cf0db16430) Merged-In: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 Change-Id: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 --- .../com/android/server/wm/DisplayPolicy.java | 9 ++++----- .../core/java/com/android/server/wm/Task.java | 16 ++++++++++++++++ .../com/android/server/wm/InsetsPolicyTest.java | 8 +++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 988e98f28d107..0da178b2bcd0a 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -2382,16 +2381,16 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final boolean multiWindowTaskVisible = + final boolean adjacentTasksVisible = defaultTaskDisplayArea.getRootTask(task -> task.isVisible() - && task.getTopLeafTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) + && task.getAdjacentTask() != null) != null; final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); // We need to force showing system bars when the multi-window or freeform root task is // visible. - mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; // We need to force the consumption of the system bars if they are force shown or if they // are controlled by a remote insets controller. mForceConsumeSystemBars = mForceShowSystemBars @@ -2412,7 +2411,7 @@ public class DisplayPolicy { int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS; appearance = configureStatusBarOpacity(appearance); - appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, + appearance = configureNavBarOpacity(appearance, adjacentTasksVisible, freeformRootTaskVisible); final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f5c44d94160b8..1ddc3e8c19b3d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2385,6 +2385,22 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } + /** @return the first adjacent task of this task or its parent. */ + @Nullable + Task getAdjacentTask() { + final TaskFragment adjacentTaskFragment = getAdjacentTaskFragment(); + if (adjacentTaskFragment != null && adjacentTaskFragment.asTask() != null) { + return adjacentTaskFragment.asTask(); + } + + final WindowContainer parent = getParent(); + if (parent == null || parent.asTask() == null) { + return null; + } + + return parent.asTask().getAdjacentTask(); + } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. boolean isRootTask() { return getRootTask() == this; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index d3282b97a6b82..9f376ad6edaba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -81,12 +81,14 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_multiWindowTaskVisible() { + public void testControlsForDispatch_adjacentTasksVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); + final Task task1 = createTask(mDisplayContent); + final Task task2 = createTask(mDisplayContent); + task1.setAdjacentTaskFragment(task2); + final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); // The app must not control any system bars. From 61717c2e63f309b31fcc73514c49ae5d2e952a13 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Tue, 18 Apr 2023 14:39:39 -0700 Subject: [PATCH 37/74] DO NOT MERGE Adding tests for updated EmptyShadeView logic Tests for ag/22688474 Bug: 267060171 Test: tests run (cherry picked from commit 6a19675b120b497618259cbcdedde4ffb68389c8) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e6fe1a0ef1ebb9123f4ed943c8470a873efbaada) Merged-In: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 Change-Id: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 --- ...cationStackScrollLayoutControllerTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 45ae96c10345c..ac60626ba768c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -294,6 +294,34 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { /* notifVisibleInShade= */ false); } + @Test + public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + mController.attach(mNotificationStackScrollLayout); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ false, + /* areNotificationsHiddenInShade= */ false); + } + + @Test + public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + mController.attach(mNotificationStackScrollLayout); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ true, + /* areNotificationsHiddenInShade= */ false); + } + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); From a810f4564c1ebfe18f1ed22ba4e446957c8f3b83 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Mon, 17 Apr 2023 15:05:23 -0700 Subject: [PATCH 38/74] DO NOT MERGE Hide EmptyShadeView when on bouncer Added a check to make sure EmptyShadeView is hidden when bouncer is showing. Bug: 267060171 Test: manually verified EmptyShadeView visibility (cherry picked from commit bef2be6757eec92391189c106e89b63176cd16c0) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2e640447d16c4e893927e2f76bda8b1073bb18f8) Merged-In: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 Change-Id: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 --- .../systemui/shade/NotificationPanelViewController.java | 1 + .../stack/NotificationStackScrollLayoutController.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7388c27ed3131..6cd0aee62551f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2843,6 +2843,7 @@ public final class NotificationPanelViewController implements Dumpable { /** Set whether the bouncer is showing. */ public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 906b9592e3ddd..f6c260a875ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1240,7 +1240,8 @@ public class NotificationStackScrollLayoutController { // Hide empty shade view when in transition to Keyguard. // That avoids "No Notifications" to blink when transitioning to AOD. // For more details, see: b/228790482 - && !isInTransitionToKeyguard(); + && !isInTransitionToKeyguard() + && !mCentralSurfaces.isBouncerShowing(); mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); From b6ea08ea6497a5dd063bd54f572dbcac440c7486 Mon Sep 17 00:00:00 2001 From: Massimo Carli Date: Wed, 26 Apr 2023 11:38:19 +0000 Subject: [PATCH 39/74] Improve first opaque activity candidate detection To catch the right candidate as first opaque activity beneath a translucent one and handle normal permission dialog behaviour and smart links. Fixes: 278661668 Test: Run `atest WmTests:SizeCompatTests` (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bcf680d064ea8921091bfc4620b6958adf79e8a2) Merged-In: I62e829555c43136080ee4909f7dcf8c388165e9f Change-Id: I62e829555c43136080ee4909f7dcf8c388165e9f --- .../com/android/server/wm/ActivityRecord.java | 2 +- .../server/wm/LetterboxUiController.java | 87 ++++++----- .../android/server/wm/SizeCompatTests.java | 142 ++++++++++++------ 3 files changed, 150 insertions(+), 81 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d872ada1fd0c3..a6236251635a9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1612,7 +1612,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParent.setResumedActivity(this, "onParentChanged"); mImeInsetsFrozenUntilStartInput = false; } - mLetterboxUiController.onActivityParentChanged(newParent); + mLetterboxUiController.updateInheritedLetterbox(); } if (rootTask != null && rootTask.topRunningActivity() == this) { diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 86bc6eeecf1f1..d69542c425486 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -112,6 +112,8 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -126,8 +128,7 @@ import java.util.function.Predicate; final class LetterboxUiController { private static final Predicate FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE = - activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing() - && activityRecord.nowVisible; + activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing(); private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; @@ -185,6 +186,10 @@ final class LetterboxUiController { // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS private final boolean mIsOverrideEnableCompatFakeFocusEnabled; + // The list of observers for the destroy event of candidate opaque activities + // when dealing with translucent activities. + private final List mDestroyListeners = new ArrayList<>(); + @Nullable private final Boolean mBooleanPropertyAllowOrientationOverride; @Nullable @@ -198,6 +203,10 @@ final class LetterboxUiController { @Nullable private WindowContainerListener mLetterboxConfigListener; + @Nullable + @VisibleForTesting + ActivityRecord mFirstOpaqueActivityBeneath; + private boolean mShowWallpaperForLetterboxBackground; // In case of transparent activities we might need to access the aspectRatio of the @@ -361,6 +370,10 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } + for (int i = mDestroyListeners.size() - 1; i >= 0; i--) { + mDestroyListeners.get(i).updateInheritedLetterbox(); + } + mDestroyListeners.clear(); if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); mLetterboxConfigListener = null; @@ -1577,7 +1590,11 @@ final class LetterboxUiController { * first opaque activity beneath. * @param parent The parent container. */ - void onActivityParentChanged(WindowContainer parent) { + void updateInheritedLetterbox() { + final WindowContainer parent = mActivityRecord.getParent(); + if (parent == null) { + return; + } if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { return; } @@ -1587,27 +1604,28 @@ final class LetterboxUiController { } // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. + mFirstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( + FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, + mActivityRecord /* boundary */, false /* includeBoundary */, + true /* traverseTopToBottom */); + if (mFirstOpaqueActivityBeneath == null || mFirstOpaqueActivityBeneath.isEmbedded()) { + // We skip letterboxing if the translucent activity doesn't have any opaque + // activities beneath or the activity below is embedded which never has letterbox. + mActivityRecord.recomputeConfiguration(); + return; + } if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return; } - final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( - FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, - mActivityRecord /* boundary */, false /* includeBoundary */, - true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) { - // We skip letterboxing if the translucent activity doesn't have any opaque - // activities beneath or the activity below is embedded which never has letterbox. - return; - } - inheritConfiguration(firstOpaqueActivityBeneath); + mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.add(this); + inheritConfiguration(mFirstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( - mActivityRecord, firstOpaqueActivityBeneath, - (opaqueConfig, transparentConfig) -> { - final Configuration mutatedConfiguration = - fromOriginalTranslucentConfig(transparentConfig); + mActivityRecord, mFirstOpaqueActivityBeneath, + (opaqueConfig, transparentOverrideConfig) -> { + resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); - final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); + final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. @@ -1616,9 +1634,9 @@ final class LetterboxUiController { parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. - mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); - inheritConfiguration(firstOpaqueActivityBeneath); - return mutatedConfiguration; + transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); + inheritConfiguration(mFirstOpaqueActivityBeneath); + return transparentOverrideConfig; }); } @@ -1691,26 +1709,19 @@ final class LetterboxUiController { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } - return Optional.ofNullable(mActivityRecord.getTask().getActivity( - FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, - mActivityRecord /* boundary */, false /* includeBoundary */, - true /* traverseTopToBottom */)); + return Optional.ofNullable(mFirstOpaqueActivityBeneath); } - // When overriding translucent activities configuration we need to keep some of the - // original properties - private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) { - final Configuration configuration = new Configuration(translucentConfig); + /** Resets the screen size related fields so they can be resolved by requested bounds later. */ + private static void resetTranslucentOverrideConfig(Configuration config) { // The values for the following properties will be defined during the configuration // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the // properties inherited from the first not finishing opaque activity beneath. - configuration.orientation = ORIENTATION_UNDEFINED; - configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; - configuration.screenHeightDp = - configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; - configuration.smallestScreenWidthDp = - configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - return configuration; + config.orientation = ORIENTATION_UNDEFINED; + config.screenWidthDp = config.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; + config.screenHeightDp = config.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; + config.smallestScreenWidthDp = config.compatSmallestScreenWidthDp = + SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; } private void inheritConfiguration(ActivityRecord firstOpaque) { @@ -1729,6 +1740,10 @@ final class LetterboxUiController { } private void clearInheritedConfig() { + if (mFirstOpaqueActivityBeneath != null) { + mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.remove(this); + } + mFirstOpaqueActivityBeneath = null; mLetterboxConfigListener = null; mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e5ad01a5b70c8..637ec7e92a7db 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -174,44 +174,6 @@ public class SizeCompatTests extends WindowTestsBase { setUpApp(builder.build()); } - @Test - public void testActivityInHistoryAndNotVisibleIsNotUsedAsOpaqueForTranslucentActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = false; - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - doReturn(false).when(translucentActivity).fillsParent(); - - mTask.addChild(translucentActivity); - - assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); - } - - @Test - public void testActivityInHistoryAndVisibleIsUsedAsOpaqueForTranslucentActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - doReturn(false).when(translucentActivity).fillsParent(); - - mTask.addChild(translucentActivity); - - assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); - } - @Test public void testCleanLetterboxConfigListenerWhenTranslucentIsDestroyed() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); @@ -236,7 +198,6 @@ public class SizeCompatTests extends WindowTestsBase { public void testHorizontalReachabilityEnabledForTranslucentActivities() { setUpDisplaySizeWithApp(2500, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; final LetterboxConfiguration config = mWm.mLetterboxConfiguration; config.setTranslucentLetterboxingOverrideEnabled(true); config.setLetterboxHorizontalPositionMultiplier(0.5f); @@ -312,7 +273,6 @@ public class SizeCompatTests extends WindowTestsBase { public void testVerticalReachabilityEnabledForTranslucentActivities() { setUpDisplaySizeWithApp(1000, 2500); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; final LetterboxConfiguration config = mWm.mLetterboxConfiguration; config.setTranslucentLetterboxingOverrideEnabled(true); config.setLetterboxVerticalPositionMultiplier(0.5f); @@ -384,6 +344,104 @@ public class SizeCompatTests extends WindowTestsBase { checkIsCentered.run(); } + @Test + public void testApplyStrategyAgainWhenOpaqueIsDestroyed() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Launch another opaque activity + final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + mTask.addChild(opaqueActivity); + // Transparent activity strategy not applied + assertFalse(opaqueActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + // Launch translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Transparent strategy applied + assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + spyOn(translucentActivity.mLetterboxUiController); + clearInvocations(translucentActivity.mLetterboxUiController); + + // We destroy the first opaque activity + opaqueActivity.setState(DESTROYED, "testing"); + opaqueActivity.removeImmediately(); + + // Check that updateInheritedLetterbox() is invoked again + verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox(); + } + + @Test + public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Launch translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Transparent strategy applied + assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + assertNotNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath); + + spyOn(translucentActivity.mLetterboxUiController); + clearInvocations(translucentActivity.mLetterboxUiController); + + // We destroy the first opaque activity + mActivity.setState(DESTROYED, "testing"); + mActivity.removeImmediately(); + + // Check that updateInheritedLetterbox() is invoked again + verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox(); + assertNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath); + } + + @Test + public void testNotApplyStrategyAgainWhenOpaqueIsNotDestroyed() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Launch another opaque activity + final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + mTask.addChild(opaqueActivity); + // Transparent activity strategy not applied + assertFalse(opaqueActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + // Launch translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Transparent strategy applied + assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + spyOn(translucentActivity.mLetterboxUiController); + clearInvocations(translucentActivity.mLetterboxUiController); + + // Check that updateInheritedLetterbox() is invoked again + verify(translucentActivity.mLetterboxUiController, never()).updateInheritedLetterbox(); + } + @Test public void testApplyStrategyToTranslucentActivities() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); @@ -391,7 +449,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); mActivity.info.setMinAspectRatio(1.2f); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) .setLaunchedFromUid(mActivity.getUid()) @@ -448,7 +505,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); mActivity.info.setMinAspectRatio(1.2f); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) .setLaunchedFromUid(mActivity.getUid()) @@ -542,7 +598,6 @@ public class SizeCompatTests extends WindowTestsBase { true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); - mActivity.nowVisible = true; prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); // We launch a transparent activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) @@ -575,7 +630,6 @@ public class SizeCompatTests extends WindowTestsBase { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); // Rotate to put activity in size compat mode. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); From ad3fb95aa2fe0be59d3e991ddc883592ab5542bc Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 4 May 2023 11:29:45 -0400 Subject: [PATCH 40/74] Invalidate buffers on transform change Fixes: 271419600 Test: repro steps on bug (cherry picked from commit 61c64472417a272ec2aa2fd38e0d1874dbf16319) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5545b1cc36fd47bb33d87875b451f9f5c870d8bf) Merged-In: Ib4eef40a0f59512c669b069532e55d36293f9e1c Change-Id: Ib4eef40a0f59512c669b069532e55d36293f9e1c --- libs/hwui/renderthread/VulkanSurface.cpp | 12 ++++++++++++ libs/hwui/renderthread/VulkanSurface.h | 1 + 2 files changed, 13 insertions(+) diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 666f329392060..cbee104aa3b01 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -368,6 +368,14 @@ void VulkanSurface::releaseBuffers() { } } +void VulkanSurface::invalidateBuffers() { + for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) { + VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i]; + bufferInfo.hasValidContents = false; + bufferInfo.lastPresentedCount = 0; + } +} + VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { // Set the mCurrentBufferInfo to invalid in case of error and only reset it to the correct // value at the end of the function if everything dequeued correctly. @@ -400,6 +408,10 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { // new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer. mWindowInfo.actualSize = actualSize; releaseBuffers(); + } else { + // A change in transform means we need to repaint the entire buffer area as the damage + // rects have just moved about. + invalidateBuffers(); } if (transformHint != mWindowInfo.transform) { diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index b8ccf7810b5d2..ff328b25d195a 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -113,6 +113,7 @@ private: WindowInfo* outWindowInfo); static bool UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo); void releaseBuffers(); + void invalidateBuffers(); // TODO: This number comes from ui/BufferQueueDefs. We're not pulling the // header in so that we don't need to depend on libui, but we should share From b18378bd79f8fba3c20e61193754d84ab66ac26a Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 18 Oct 2022 05:21:30 +0000 Subject: [PATCH 41/74] Ensure that only SysUI can override pending intent launch flags - Originally added in ag/5139951, this method ensured that activities launched from widgets are always started in a new task (if the activity is launched in the home task, the task is not brough forward with the recents transition). We can restrict this to only recents callers since this only applies to 1p launchers in gesture nav (both the gesture with 3p launchers and button nav in general will always start the home intent directly, which makes adding the NEW_TASK flag unnecessary). Bug: 243794108 Test: Ensure that the original bug b/112508020 still works (with the test app in the bug, swipe up still works after launching an activity from the widget, and fails without applying the override flags) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c4d3106e347922610f8c554de3ae238175ed393e) Merged-In: Id53c6a2aa6da5933d488ca06a0bfc4ef89a4c343 Change-Id: Id53c6a2aa6da5933d488ca06a0bfc4ef89a4c343 --- .../com/android/server/am/PendingIntentRecord.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index bda60ff2172b3..8624ee031a936 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,11 +379,16 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is to ensure that the caller - // can specify a consistent launch mode even if the PendingIntent is immutable + // Apply any launch flags from the ActivityOptions. This is used only by SystemUI + // to ensure that we can launch the pending intent with a consistent launch mode even + // if the provided PendingIntent is immutable (ie. to force an activity to launch into + // a new task, or to launch multiple instances if supported by the app) final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + // TODO(b/254490217): Move this check into SafeActivityOptions + if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + } } // Extract options before clearing calling identity From 8d4767b63da511f057f6b835ea56ac6a9041ad3f Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 11 Jan 2023 18:58:41 +0000 Subject: [PATCH 42/74] Revert "Ensure that only SysUI can override pending intent launch flags" This reverts commit c4d3106e347922610f8c554de3ae238175ed393e. Reason for revert: b/264884187, b/264885689 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48acfb0f1d71912e757cadd505901471c1df4d4c) Merged-In: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba Change-Id: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba --- .../com/android/server/am/PendingIntentRecord.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 8624ee031a936..bda60ff2172b3 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,16 +379,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is used only by SystemUI - // to ensure that we can launch the pending intent with a consistent launch mode even - // if the provided PendingIntent is immutable (ie. to force an activity to launch into - // a new task, or to launch multiple instances if supported by the app) + // Apply any launch flags from the ActivityOptions. This is to ensure that the caller + // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - // TODO(b/254490217): Move this check into SafeActivityOptions - if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); - } + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } // Extract options before clearing calling identity From b6e862d452918c3cc25429bb49239c5abad2164e Mon Sep 17 00:00:00 2001 From: Lucas Lin Date: Fri, 3 Mar 2023 08:13:50 +0000 Subject: [PATCH 43/74] Sanitize VPN label to prevent HTML injection This commit will try to sanitize the content of VpnDialog. This commit creates a function which will try to sanitize the VPN label, if the sanitized VPN label is different from the original one, which means the VPN label might contain HTML tag or the VPN label violates the words restriction(may contain some wording which will mislead the user). For this kind of case, show the package name instead of the VPN label to prevent misleading the user. The malicious VPN app might be able to add a large number of line breaks with HTML in order to hide the system-displayed text from the user in the connection request dialog. Thus, sanitizing the content of the dialog is needed. Bug: 204554636 Test: N/A (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2178216b98bf9865edee198f45192f0b883624ab) Merged-In: I8eb890fd2e5797d8d6ab5b12f9c628bc9616081d Change-Id: I8eb890fd2e5797d8d6ab5b12f9c628bc9616081d --- .../VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index 2b3202e0a9822..2f3a12040c886 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -52,6 +52,8 @@ public class ConfirmDialog extends AlertActivity private View mView; + private View mView; + public ConfirmDialog() { this(VpnManager.TYPE_VPN_SERVICE); } From 1c3625e8258b49d3a89c9cce8fddedaa34af0e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 13 Apr 2023 17:58:22 +0200 Subject: [PATCH 44/74] Grant URI permissions to the CallStyle-related ones This will also verify that the caller app can actually grant them. Fix: 274592467 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4dee5aab12e95cd8b4d663ad050f07b0f2433596) Merged-In: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc Change-Id: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc --- core/java/android/app/Notification.java | 8 +++++++ .../NotificationManagerServiceTest.java | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7f48e39a9a797..eb9567c26d8df 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2883,6 +2883,14 @@ public class Notification implements Parcelable } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } 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 874846d333cfd..6827bbe1b43bd 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5390,6 +5390,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(backgroundImage)); } + @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); From 8379fee09b56bad852f106bb65175d3fce17da5e Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 8 Feb 2023 01:04:46 +0000 Subject: [PATCH 45/74] Only allow NEW_TASK flag when adjusting pending intents Bug: 243794108 Test: atest CtsSecurityBulletinHostTestCases:android.security.cts.CVE_2023_20918 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c62d2e1021a030f4f0ae5fcfc8fe8e0875fa669f) Merged-In: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b Change-Id: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b --- core/java/android/app/ActivityOptions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d6f44e60eb0c2..2540f1dea732e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -21,6 +21,8 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; @@ -1727,7 +1729,9 @@ public class ActivityOptions extends ComponentOptions { * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** From 85ff5495758bc69243377318f10773d0badd59a5 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 28 Mar 2023 13:15:04 -0700 Subject: [PATCH 46/74] Dismiss keyguard when simpin auth'd and... security method is none. This is mostly to fix the case where we auth sim pin in the set up wizard and it goes straight to keyguard instead of the setup wizard activity. This works with the prevent bypass keyguard flag because the device should be noe secure in this case. Fixes: 222446076 Test: turn locked sim on, which opens the sim pin screen. Auth the screen and observe that keyguard is not shown. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48fa9bef3451e4a358c941af5b230f99881c5cb6) Cherry-picking this CL as a security fix Bug: 222446076 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:65ea56f54c059584eb27ec53d486dba8161316ab) Merged-In: Id302c41f63028bc6dd58ba686e23d73565de9675 Change-Id: Id302c41f63028bc6dd58ba686e23d73565de9675 --- .../android/keyguard/KeyguardSecurityContainerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 061bab8a7006b..16299c7aff7b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -752,7 +752,7 @@ public class KeyguardSecurityContainerController extends ViewController Date: Fri, 21 Apr 2023 09:45:07 +0000 Subject: [PATCH 47/74] Truncate ShortcutInfo Id Creating Conversation with a ShortcutId longer than 65_535 (max unsigned short), we did not save the conversation settings into the notification_policy.xml due to a restriction in FastDataOutput. This put us to a state where the user changing the importance or turning off the notifications for the given conversation had no effect on notification behavior. Fixes: 273729476 Test: atest ShortcutManagerTest2 Test: Create a test app which creates a Conversation with a long shortcutId. Go to the Conversation Settings and turn off Notifications. Post a new Notification to this Conversation and see if it is displayed. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8a5223fcd59e805da510c31c5f8462c5d01ec6d5) Merged-In: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 Change-Id: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 --- .../java/android/content/pm/ShortcutInfo.java | 20 ++++++++++++++++--- .../server/pm/ShortcutManagerTest2.java | 9 +++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 52774e354c901..c33390398400e 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -283,6 +283,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -475,8 +481,7 @@ public final class ShortcutInfo implements Parcelable { private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -582,6 +587,14 @@ public final class ShortcutInfo implements Parcelable { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2336,7 +2349,8 @@ public final class ShortcutInfo implements Parcelable { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl, android.content.ComponentName.class); mFlags = source.readInt(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index c78678431dacd..86d4655e9d3a6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -228,6 +228,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + "s".repeat(Short.MAX_VALUE)).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) From b2fa796bcfe4a57b97aa25d9c9233bca431c642e Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 12 Apr 2022 17:36:41 -0700 Subject: [PATCH 48/74] Watch uid proc state instead of importance for 1-time permissions The system process may bind to an app with the flag BIND_FOREGROUND_SERVICE, this will put the client in the foreground service importance level without the normal requirement that foreground services must show a notification. Looking at proc states instead allows us to differentiate between these two levels of foreground service and revoke the client when not in use. This change makes the parameters `importanceToResetTimer` and `importanceToKeepSessionAlive` in PermissionManager#startOneTimePermissionSession obsolete. Test: atest CtsPermissionTestCases + manual testing with mic/cam/loc Bug: 217981062 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0be78fbbf7d92bf29858aa0c48b171045ab5057f) Merged-In: I7a725647c001062d1a76a82b680a02e3e2edcb03 Change-Id: I7a725647c001062d1a76a82b680a02e3e2edcb03 --- .../permission/IPermissionManager.aidl | 3 +- .../android/permission/PermissionManager.java | 3 +- .../OneTimePermissionUserManager.java | 227 +++++++++--------- .../permission/PermissionManagerService.java | 6 +- 4 files changed, 114 insertions(+), 125 deletions(-) diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 6a93b354f4dab..45dad9861406a 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -77,8 +77,7 @@ interface IPermissionManager { List getSplitPermissions(); void startOneTimePermissionSession(String packageName, int userId, long timeout, - long revokeAfterKilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive); + long revokeAfterKilledDelay); void stopOneTimePermissionSession(String packageName, int userId); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 6b540d72bba00..67699543131ae 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1371,8 +1371,7 @@ public final class PermissionManager { @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { try { mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(), - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 881f8707fdd86..a1c98109052e2 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -16,17 +16,18 @@ package com.android.server.pm.permission; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; - import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.IActivityManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Handler; +import android.os.RemoteException; import android.permission.PermissionControllerManager; import android.provider.DeviceConfig; import android.util.Log; @@ -47,7 +48,7 @@ public class OneTimePermissionUserManager { "one_time_permissions_killed_delay_millis"; private final @NonNull Context mContext; - private final @NonNull ActivityManager mActivityManager; + private final @NonNull IActivityManager mIActivityManager; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -77,49 +78,14 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; - mActivityManager = context.getSystemService(ActivityManager.class); + mIActivityManager = ActivityManager.getService(); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); } - /** - * Starts a one-time permission session for a given package. A one-time permission session is - * ended if app becomes inactive. Inactivity is defined as the package's uid importance level - * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid - * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start - * until going > importanceToResetTimer. - *

- * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive - * then the session is extended until either the importance goes above - * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which - * will continue the session and reset the timer. - *

- *

- * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}. - *

- *

- * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout - * is invoked. - *

- *

- * Note that if there is currently an active session for a package a new one isn't created and - * the existing one isn't changed. - *

- * @param packageName The package to start a one-time permission session for - * @param timeoutMillis Number of milliseconds for an app to be in an inactive state - * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies - * before ending the session. Set to -1 to use default value - * for the device. - * @param importanceToResetTimer The least important level to uid must be to reset the timer - * @param importanceToKeepSessionAlive The least important level the uid must be to keep the - * session alive - * - * @hide - */ void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, - long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long revokeAfterKilledDelayMillis) { int uid; try { uid = mContext.getPackageManager().getPackageUid(packageName, 0); @@ -131,13 +97,11 @@ public class OneTimePermissionUserManager { synchronized (mLock) { PackageInactivityListener listener = mListeners.get(uid); if (listener != null) { - listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis, - importanceToResetTimer, importanceToKeepSessionAlive); + listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis); return; } listener = new PackageInactivityListener(uid, packageName, timeoutMillis, - revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + revokeAfterKilledDelayMillis); mListeners.put(uid, listener); } } @@ -182,34 +146,58 @@ public class OneTimePermissionUserManager { private static final long TIMER_INACTIVE = -1; + private static final int STATE_GONE = 0; + private static final int STATE_TIMER = 1; + private static final int STATE_ACTIVE = 2; + private final int mUid; private final @NonNull String mPackageName; private long mTimeout; private long mRevokeAfterKilledDelay; - private int mImportanceToResetTimer; - private int mImportanceToKeepSessionAlive; private boolean mIsAlarmSet; private boolean mIsFinished; private long mTimerStart = TIMER_INACTIVE; - private final ActivityManager.OnUidImportanceListener mStartTimerListener; - private final ActivityManager.OnUidImportanceListener mSessionKillableListener; - private final ActivityManager.OnUidImportanceListener mGoneListener; - private final Object mInnerLock = new Object(); private final Object mToken = new Object(); + private final IUidObserver.Stub mObserver = new IUidObserver.Stub() { + @Override + public void onUidGone(int uid, boolean disabled) { + if (uid == mUid) { + PackageInactivityListener.this.updateUidState(STATE_GONE); + } + } + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, + int capability) { + if (uid == mUid) { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) { + PackageInactivityListener.this.updateUidState(STATE_TIMER); + } else { + PackageInactivityListener.this.updateUidState(STATE_ACTIVE); + } + } + } + + public void onUidActive(int uid) { + } + public void onUidIdle(int uid, boolean disabled) { + } + public void onUidProcAdjChanged(int uid) { + } + public void onUidCachedChanged(int uid, boolean cached) { + } + }; private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, - long revokeAfterkilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive) { - + long revokeAfterkilledDelay) { Log.i(LOG_TAG, "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout - + " killedDelay=" + revokeAfterkilledDelay - + " importanceToResetTimer=" + importanceToResetTimer - + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); + + " killedDelay=" + revokeAfterkilledDelay); mUid = uid; mPackageName = packageName; @@ -219,27 +207,24 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterkilledDelay; - mImportanceToResetTimer = importanceToResetTimer; - mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; - mStartTimerListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mSessionKillableListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mGoneListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); + try { + mIActivityManager.registerUidObserver(mObserver, + ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + null); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); + // Can't register uid observer, just revoke immediately + synchronized (mInnerLock) { + onPackageInactiveLocked(); + } + } - mActivityManager.addOnUidImportanceListener(mStartTimerListener, - importanceToResetTimer); - mActivityManager.addOnUidImportanceListener(mSessionKillableListener, - importanceToKeepSessionAlive); - mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED); - - onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); + updateUidState(); } - public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis, - int importanceToResetTimer, int importanceToKeepSessionAlive) { + public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) { synchronized (mInnerLock) { mTimeout = Math.min(mTimeout, timeoutMillis); mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay, @@ -248,63 +233,79 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterKilledDelayMillis); - mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer); - mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive, - mImportanceToKeepSessionAlive); Log.v(LOG_TAG, "Updated params for " + mPackageName + ". timeout=" + mTimeout - + " killedDelay=" + mRevokeAfterKilledDelay - + " importanceToResetTimer=" + mImportanceToResetTimer - + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive); - onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName)); + + " killedDelay=" + mRevokeAfterKilledDelay); + updateUidState(); } } - private void onImportanceChanged(int uid, int importance) { - if (uid != mUid) { - return; + private int getCurrentState() { + try { + return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null)); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); } + return STATE_GONE; + } - Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")." - + " importance=" + importance); + private int getStateFromProcState(int procState) { + if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) { + return STATE_GONE; + } else { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + return STATE_TIMER; + } else { + return STATE_ACTIVE; + } + } + } + + private void updateUidState() { + updateUidState(getCurrentState()); + } + + private void updateUidState(int state) { + Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")." + + " state=" + state); synchronized (mInnerLock) { // Remove any pending inactivity callback mHandler.removeCallbacksAndMessages(mToken); - if (importance > IMPORTANCE_CACHED) { + if (state == STATE_GONE) { if (mRevokeAfterKilledDelay == 0) { onPackageInactiveLocked(); return; } // Delay revocation in case app is restarting mHandler.postDelayed(() -> { - int imp = mActivityManager.getUidImportance(mUid); - if (imp > IMPORTANCE_CACHED) { - onPackageInactiveLocked(); - } else { - if (DEBUG) { - Log.d(LOG_TAG, "No longer gone after delayed revocation. " - + "Rechecking for " + mPackageName + " (" + mUid + ")."); + int currentState; + synchronized (mInnerLock) { + currentState = getCurrentState(); + if (currentState == STATE_GONE) { + onPackageInactiveLocked(); + return; } - onImportanceChanged(mUid, imp); } + if (DEBUG) { + Log.d(LOG_TAG, "No longer gone after delayed revocation. " + + "Rechecking for " + mPackageName + " (" + mUid + + ")."); + } + updateUidState(currentState); }, mToken, mRevokeAfterKilledDelay); return; - } - if (importance > mImportanceToResetTimer) { + } else if (state == STATE_TIMER) { if (mTimerStart == TIMER_INACTIVE) { if (DEBUG) { Log.d(LOG_TAG, "Start the timer for " + mPackageName + " (" + mUid + ")."); } mTimerStart = System.currentTimeMillis(); + setAlarmLocked(); } - } else { + } else if (state == STATE_ACTIVE) { mTimerStart = TIMER_INACTIVE; - } - if (importance > mImportanceToKeepSessionAlive) { - setAlarmLocked(); - } else { cancelAlarmLocked(); } } @@ -318,19 +319,9 @@ public class OneTimePermissionUserManager { mIsFinished = true; cancelAlarmLocked(); try { - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove start timer listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove session killable listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mGoneListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove gone listener", e); + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); } } } @@ -394,9 +385,11 @@ public class OneTimePermissionUserManager { mPermissionControllerManager.notifyOneTimePermissionSessionTimeout( mPackageName); }); - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - mActivityManager.removeOnUidImportanceListener(mGoneListener); + try { + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); + } synchronized (mLock) { mListeners.remove(mUid); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d648d6fc663d4..fd48cdaeebf2d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -386,8 +386,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, - long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long timeoutMillis, long revokeAfterKilledDelayMillis) { mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS @@ -397,8 +396,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long token = Binder.clearCallingIdentity(); try { getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName, - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } finally { Binder.restoreCallingIdentity(token); } From b93922788a8a60ca7e5164a05601eb746c2a0994 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 31 Jan 2023 17:14:34 -0800 Subject: [PATCH 49/74] [1-time permissions] Use internal api to check proc states We need to check the proc state and the binder method has a filter that is affected by a bug that keeps a killed a proces in the "pending top" list. Using the internal api isn't affected by this filter and also is more correct for inprocess calls. Test: Install test app that requests permission and will exit immediately on granting, observe permission is no longer indefinitely held. Bug: 254736794 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e836611f3057cf9eae589a34a39fe80d0a9145f3) Merged-In: I30579090c803b231fd750abbc4ad645805f7ece2 Change-Id: I30579090c803b231fd750abbc4ad645805f7ece2 --- .../pm/permission/OneTimePermissionUserManager.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index a1c98109052e2..d28048ce74c79 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -18,6 +18,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.IUidObserver; @@ -34,6 +35,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; /** * Class that handles one-time permissions for a user @@ -49,6 +51,7 @@ public class OneTimePermissionUserManager { private final @NonNull Context mContext; private final @NonNull IActivityManager mIActivityManager; + private final @NonNull ActivityManagerInternal mActivityManagerInternal; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -79,6 +82,7 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; mIActivityManager = ActivityManager.getService(); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); @@ -241,12 +245,7 @@ public class OneTimePermissionUserManager { } private int getCurrentState() { - try { - return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null)); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Couldn't check uid proc state", e); - } - return STATE_GONE; + return getStateFromProcState(mActivityManagerInternal.getUidProcessState(mUid)); } private int getStateFromProcState(int procState) { From da894ded77b2a8cf2c686c919e6120e65ee66c8c Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 21 Apr 2023 15:39:22 +0000 Subject: [PATCH 50/74] Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added the person URIs in the test, since they weren't being checked. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:43b1711332763788c7abf05c3baa931296c45bbb) Merged-In: I848545f7aee202495c515f47a32871a2cb6ae707 Change-Id: I848545f7aee202495c515f47a32871a2cb6ae707 --- core/java/android/app/Notification.java | 11 +++++++ .../NotificationManagerServiceTest.java | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index eb9567c26d8df..de8fb509bde93 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2853,6 +2853,17 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { 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 6827bbe1b43bd..8f8b1c50891ac 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -128,6 +128,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -5373,10 +5374,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -5388,6 +5415,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); } @Test From b68cd812d0eac51f678736317013a943b8ba2ac0 Mon Sep 17 00:00:00 2001 From: Sumedh Sen Date: Thu, 23 Mar 2023 16:29:47 -0700 Subject: [PATCH 51/74] [RESTRICT AUTOMERGE] Prevent installing apps in policy restricted work profile using ADB If DISALLOW_DEBUGGING_FEATURES or DISALLOW_INSTALL_APPS restrictions are set on a work profile, prevent side loading of APKs using ADB in the work profile. Bug: 257443065 Test: atest CtsPackageInstallTestCases:UserRestrictionInstallTest (cherry picked from commit febe3918020a94b2af48ade98eb6a49cdd4a3bdf) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b988a09db551d9a8b2aeb0e8eb88e610605709e8) Merged-In: I169a1f72c84528ca606b6a4da165d4fbcd02b08d Change-Id: I169a1f72c84528ca606b6a4da165d4fbcd02b08d --- .../server/pm/InstallPackageHelper.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c32a57c68ede3..259701166147f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2093,9 +2093,25 @@ final class InstallPackageHelper { // The caller explicitly specified INSTALL_ALL_USERS flag. // Thus, updating the settings to install the app for all users. for (int currentUserId : allUsers) { - ps.setInstalled(true, currentUserId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, - installerPackageName); + // If the app is already installed for the currentUser, + // keep it as installed as we might be updating the app at this place. + // If not currently installed, check if the currentUser is restricted by + // DISALLOW_INSTALL_APPS or DISALLOW_DEBUGGING_FEATURES device policy. + // Install / update the app if the user isn't restricted. Skip otherwise. + final boolean installedForCurrentUser = ArrayUtils.contains( + installedForUsers, currentUserId); + final boolean restrictedByPolicy = + mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_INSTALL_APPS) + || mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_DEBUGGING_FEATURES); + if (installedForCurrentUser || !restrictedByPolicy) { + ps.setInstalled(true, currentUserId); + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId, + installerPackageName); + } else { + ps.setInstalled(false, currentUserId); + } } } From cd82ce44fbec97321802a1f1809650c90291f9e9 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 12:36:05 +0000 Subject: [PATCH 52/74] Visit URIs in landscape/portrait custom remote views. Bug: 277740848 Test: atest RemoteViewsTest NotificationManagerServiceTest & tested with POC from bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b4692946c10d11c1e935869e11dc709a9cdcba69) Merged-In: I7d3d35df0ec38945019f71755bed8797b7af4517 Change-Id: I7d3d35df0ec38945019f71755bed8797b7af4517 --- core/java/android/widget/RemoteViews.java | 6 ++ .../src/android/widget/RemoteViewsTest.java | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a339062677361..c985ae82307c2 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer visitor) { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 00b3693c902bb..a9d3ce51b2c3d 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -33,6 +37,8 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; @@ -58,6 +64,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -690,4 +697,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } From 0c2215c1bdadaa96b54e78299624203a6b887f72 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 18 Oct 2022 05:21:30 +0000 Subject: [PATCH 53/74] Ensure that only SysUI can override pending intent launch flags - Originally added in ag/5139951, this method ensured that activities launched from widgets are always started in a new task (if the activity is launched in the home task, the task is not brough forward with the recents transition). We can restrict this to only recents callers since this only applies to 1p launchers in gesture nav (both the gesture with 3p launchers and button nav in general will always start the home intent directly, which makes adding the NEW_TASK flag unnecessary). Bug: 243794108 Test: Ensure that the original bug b/112508020 still works (with the test app in the bug, swipe up still works after launching an activity from the widget, and fails without applying the override flags) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c4d3106e347922610f8c554de3ae238175ed393e) Merged-In: Id53c6a2aa6da5933d488ca06a0bfc4ef89a4c343 Change-Id: Id53c6a2aa6da5933d488ca06a0bfc4ef89a4c343 --- .../com/android/server/am/PendingIntentRecord.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index bda60ff2172b3..8624ee031a936 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,11 +379,16 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is to ensure that the caller - // can specify a consistent launch mode even if the PendingIntent is immutable + // Apply any launch flags from the ActivityOptions. This is used only by SystemUI + // to ensure that we can launch the pending intent with a consistent launch mode even + // if the provided PendingIntent is immutable (ie. to force an activity to launch into + // a new task, or to launch multiple instances if supported by the app) final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + // TODO(b/254490217): Move this check into SafeActivityOptions + if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + } } // Extract options before clearing calling identity From 0c4f9bd61a5f632be9610f7800a1a4c20e741236 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 11 Jan 2023 18:58:41 +0000 Subject: [PATCH 54/74] Revert "Ensure that only SysUI can override pending intent launch flags" This reverts commit c4d3106e347922610f8c554de3ae238175ed393e. Reason for revert: b/264884187, b/264885689 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48acfb0f1d71912e757cadd505901471c1df4d4c) Merged-In: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba Change-Id: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba --- .../com/android/server/am/PendingIntentRecord.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 8624ee031a936..bda60ff2172b3 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,16 +379,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is used only by SystemUI - // to ensure that we can launch the pending intent with a consistent launch mode even - // if the provided PendingIntent is immutable (ie. to force an activity to launch into - // a new task, or to launch multiple instances if supported by the app) + // Apply any launch flags from the ActivityOptions. This is to ensure that the caller + // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - // TODO(b/254490217): Move this check into SafeActivityOptions - if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); - } + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } // Extract options before clearing calling identity From a067bd4266a1f80b4c1d29c3595d9bd6c32d0fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 13 Apr 2023 17:58:22 +0200 Subject: [PATCH 55/74] Grant URI permissions to the CallStyle-related ones This will also verify that the caller app can actually grant them. Fix: 274592467 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4dee5aab12e95cd8b4d663ad050f07b0f2433596) Merged-In: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc Change-Id: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc --- core/java/android/app/Notification.java | 8 +++++++ .../NotificationManagerServiceTest.java | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7f48e39a9a797..eb9567c26d8df 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2883,6 +2883,14 @@ public class Notification implements Parcelable } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } 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 874846d333cfd..6827bbe1b43bd 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5390,6 +5390,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(backgroundImage)); } + @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); From c14de1413b4baf6de7f66da990b123911ed42239 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 8 Feb 2023 01:04:46 +0000 Subject: [PATCH 56/74] Only allow NEW_TASK flag when adjusting pending intents Bug: 243794108 Test: atest CtsSecurityBulletinHostTestCases:android.security.cts.CVE_2023_20918 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c62d2e1021a030f4f0ae5fcfc8fe8e0875fa669f) Merged-In: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b Change-Id: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b --- core/java/android/app/ActivityOptions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d6f44e60eb0c2..2540f1dea732e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -21,6 +21,8 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; @@ -1727,7 +1729,9 @@ public class ActivityOptions extends ComponentOptions { * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** From 67d1b351ea37c967b0632da1e0bbf7f5d1c06dcc Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 28 Mar 2023 13:15:04 -0700 Subject: [PATCH 57/74] Dismiss keyguard when simpin auth'd and... security method is none. This is mostly to fix the case where we auth sim pin in the set up wizard and it goes straight to keyguard instead of the setup wizard activity. This works with the prevent bypass keyguard flag because the device should be noe secure in this case. Fixes: 222446076 Test: turn locked sim on, which opens the sim pin screen. Auth the screen and observe that keyguard is not shown. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48fa9bef3451e4a358c941af5b230f99881c5cb6) Cherry-picking this CL as a security fix Bug: 222446076 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:65ea56f54c059584eb27ec53d486dba8161316ab) Merged-In: Id302c41f63028bc6dd58ba686e23d73565de9675 Change-Id: Id302c41f63028bc6dd58ba686e23d73565de9675 --- .../android/keyguard/KeyguardSecurityContainerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 061bab8a7006b..16299c7aff7b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -752,7 +752,7 @@ public class KeyguardSecurityContainerController extends ViewController Date: Fri, 21 Apr 2023 09:45:07 +0000 Subject: [PATCH 58/74] Truncate ShortcutInfo Id Creating Conversation with a ShortcutId longer than 65_535 (max unsigned short), we did not save the conversation settings into the notification_policy.xml due to a restriction in FastDataOutput. This put us to a state where the user changing the importance or turning off the notifications for the given conversation had no effect on notification behavior. Fixes: 273729476 Test: atest ShortcutManagerTest2 Test: Create a test app which creates a Conversation with a long shortcutId. Go to the Conversation Settings and turn off Notifications. Post a new Notification to this Conversation and see if it is displayed. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8a5223fcd59e805da510c31c5f8462c5d01ec6d5) Merged-In: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 Change-Id: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 --- .../java/android/content/pm/ShortcutInfo.java | 20 ++++++++++++++++--- .../server/pm/ShortcutManagerTest2.java | 9 +++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 52774e354c901..c33390398400e 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -283,6 +283,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -475,8 +481,7 @@ public final class ShortcutInfo implements Parcelable { private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -582,6 +587,14 @@ public final class ShortcutInfo implements Parcelable { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2336,7 +2349,8 @@ public final class ShortcutInfo implements Parcelable { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl, android.content.ComponentName.class); mFlags = source.readInt(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index c78678431dacd..86d4655e9d3a6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -228,6 +228,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + "s".repeat(Short.MAX_VALUE)).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) From 3b3110b5455b9323ecc33a2283ef890b921c8e72 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 12 Apr 2022 17:36:41 -0700 Subject: [PATCH 59/74] Watch uid proc state instead of importance for 1-time permissions The system process may bind to an app with the flag BIND_FOREGROUND_SERVICE, this will put the client in the foreground service importance level without the normal requirement that foreground services must show a notification. Looking at proc states instead allows us to differentiate between these two levels of foreground service and revoke the client when not in use. This change makes the parameters `importanceToResetTimer` and `importanceToKeepSessionAlive` in PermissionManager#startOneTimePermissionSession obsolete. Test: atest CtsPermissionTestCases + manual testing with mic/cam/loc Bug: 217981062 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0be78fbbf7d92bf29858aa0c48b171045ab5057f) Merged-In: I7a725647c001062d1a76a82b680a02e3e2edcb03 Change-Id: I7a725647c001062d1a76a82b680a02e3e2edcb03 --- .../permission/IPermissionManager.aidl | 3 +- .../android/permission/PermissionManager.java | 3 +- .../OneTimePermissionUserManager.java | 227 +++++++++--------- .../permission/PermissionManagerService.java | 6 +- 4 files changed, 114 insertions(+), 125 deletions(-) diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 6a93b354f4dab..45dad9861406a 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -77,8 +77,7 @@ interface IPermissionManager { List getSplitPermissions(); void startOneTimePermissionSession(String packageName, int userId, long timeout, - long revokeAfterKilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive); + long revokeAfterKilledDelay); void stopOneTimePermissionSession(String packageName, int userId); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 6b540d72bba00..67699543131ae 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1371,8 +1371,7 @@ public final class PermissionManager { @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { try { mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(), - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 881f8707fdd86..a1c98109052e2 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -16,17 +16,18 @@ package com.android.server.pm.permission; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; - import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.IActivityManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Handler; +import android.os.RemoteException; import android.permission.PermissionControllerManager; import android.provider.DeviceConfig; import android.util.Log; @@ -47,7 +48,7 @@ public class OneTimePermissionUserManager { "one_time_permissions_killed_delay_millis"; private final @NonNull Context mContext; - private final @NonNull ActivityManager mActivityManager; + private final @NonNull IActivityManager mIActivityManager; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -77,49 +78,14 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; - mActivityManager = context.getSystemService(ActivityManager.class); + mIActivityManager = ActivityManager.getService(); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); } - /** - * Starts a one-time permission session for a given package. A one-time permission session is - * ended if app becomes inactive. Inactivity is defined as the package's uid importance level - * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid - * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start - * until going > importanceToResetTimer. - *

- * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive - * then the session is extended until either the importance goes above - * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which - * will continue the session and reset the timer. - *

- *

- * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}. - *

- *

- * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout - * is invoked. - *

- *

- * Note that if there is currently an active session for a package a new one isn't created and - * the existing one isn't changed. - *

- * @param packageName The package to start a one-time permission session for - * @param timeoutMillis Number of milliseconds for an app to be in an inactive state - * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies - * before ending the session. Set to -1 to use default value - * for the device. - * @param importanceToResetTimer The least important level to uid must be to reset the timer - * @param importanceToKeepSessionAlive The least important level the uid must be to keep the - * session alive - * - * @hide - */ void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, - long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long revokeAfterKilledDelayMillis) { int uid; try { uid = mContext.getPackageManager().getPackageUid(packageName, 0); @@ -131,13 +97,11 @@ public class OneTimePermissionUserManager { synchronized (mLock) { PackageInactivityListener listener = mListeners.get(uid); if (listener != null) { - listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis, - importanceToResetTimer, importanceToKeepSessionAlive); + listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis); return; } listener = new PackageInactivityListener(uid, packageName, timeoutMillis, - revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + revokeAfterKilledDelayMillis); mListeners.put(uid, listener); } } @@ -182,34 +146,58 @@ public class OneTimePermissionUserManager { private static final long TIMER_INACTIVE = -1; + private static final int STATE_GONE = 0; + private static final int STATE_TIMER = 1; + private static final int STATE_ACTIVE = 2; + private final int mUid; private final @NonNull String mPackageName; private long mTimeout; private long mRevokeAfterKilledDelay; - private int mImportanceToResetTimer; - private int mImportanceToKeepSessionAlive; private boolean mIsAlarmSet; private boolean mIsFinished; private long mTimerStart = TIMER_INACTIVE; - private final ActivityManager.OnUidImportanceListener mStartTimerListener; - private final ActivityManager.OnUidImportanceListener mSessionKillableListener; - private final ActivityManager.OnUidImportanceListener mGoneListener; - private final Object mInnerLock = new Object(); private final Object mToken = new Object(); + private final IUidObserver.Stub mObserver = new IUidObserver.Stub() { + @Override + public void onUidGone(int uid, boolean disabled) { + if (uid == mUid) { + PackageInactivityListener.this.updateUidState(STATE_GONE); + } + } + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, + int capability) { + if (uid == mUid) { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) { + PackageInactivityListener.this.updateUidState(STATE_TIMER); + } else { + PackageInactivityListener.this.updateUidState(STATE_ACTIVE); + } + } + } + + public void onUidActive(int uid) { + } + public void onUidIdle(int uid, boolean disabled) { + } + public void onUidProcAdjChanged(int uid) { + } + public void onUidCachedChanged(int uid, boolean cached) { + } + }; private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, - long revokeAfterkilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive) { - + long revokeAfterkilledDelay) { Log.i(LOG_TAG, "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout - + " killedDelay=" + revokeAfterkilledDelay - + " importanceToResetTimer=" + importanceToResetTimer - + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); + + " killedDelay=" + revokeAfterkilledDelay); mUid = uid; mPackageName = packageName; @@ -219,27 +207,24 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterkilledDelay; - mImportanceToResetTimer = importanceToResetTimer; - mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; - mStartTimerListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mSessionKillableListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mGoneListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); + try { + mIActivityManager.registerUidObserver(mObserver, + ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + null); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); + // Can't register uid observer, just revoke immediately + synchronized (mInnerLock) { + onPackageInactiveLocked(); + } + } - mActivityManager.addOnUidImportanceListener(mStartTimerListener, - importanceToResetTimer); - mActivityManager.addOnUidImportanceListener(mSessionKillableListener, - importanceToKeepSessionAlive); - mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED); - - onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); + updateUidState(); } - public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis, - int importanceToResetTimer, int importanceToKeepSessionAlive) { + public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) { synchronized (mInnerLock) { mTimeout = Math.min(mTimeout, timeoutMillis); mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay, @@ -248,63 +233,79 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterKilledDelayMillis); - mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer); - mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive, - mImportanceToKeepSessionAlive); Log.v(LOG_TAG, "Updated params for " + mPackageName + ". timeout=" + mTimeout - + " killedDelay=" + mRevokeAfterKilledDelay - + " importanceToResetTimer=" + mImportanceToResetTimer - + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive); - onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName)); + + " killedDelay=" + mRevokeAfterKilledDelay); + updateUidState(); } } - private void onImportanceChanged(int uid, int importance) { - if (uid != mUid) { - return; + private int getCurrentState() { + try { + return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null)); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); } + return STATE_GONE; + } - Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")." - + " importance=" + importance); + private int getStateFromProcState(int procState) { + if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) { + return STATE_GONE; + } else { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + return STATE_TIMER; + } else { + return STATE_ACTIVE; + } + } + } + + private void updateUidState() { + updateUidState(getCurrentState()); + } + + private void updateUidState(int state) { + Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")." + + " state=" + state); synchronized (mInnerLock) { // Remove any pending inactivity callback mHandler.removeCallbacksAndMessages(mToken); - if (importance > IMPORTANCE_CACHED) { + if (state == STATE_GONE) { if (mRevokeAfterKilledDelay == 0) { onPackageInactiveLocked(); return; } // Delay revocation in case app is restarting mHandler.postDelayed(() -> { - int imp = mActivityManager.getUidImportance(mUid); - if (imp > IMPORTANCE_CACHED) { - onPackageInactiveLocked(); - } else { - if (DEBUG) { - Log.d(LOG_TAG, "No longer gone after delayed revocation. " - + "Rechecking for " + mPackageName + " (" + mUid + ")."); + int currentState; + synchronized (mInnerLock) { + currentState = getCurrentState(); + if (currentState == STATE_GONE) { + onPackageInactiveLocked(); + return; } - onImportanceChanged(mUid, imp); } + if (DEBUG) { + Log.d(LOG_TAG, "No longer gone after delayed revocation. " + + "Rechecking for " + mPackageName + " (" + mUid + + ")."); + } + updateUidState(currentState); }, mToken, mRevokeAfterKilledDelay); return; - } - if (importance > mImportanceToResetTimer) { + } else if (state == STATE_TIMER) { if (mTimerStart == TIMER_INACTIVE) { if (DEBUG) { Log.d(LOG_TAG, "Start the timer for " + mPackageName + " (" + mUid + ")."); } mTimerStart = System.currentTimeMillis(); + setAlarmLocked(); } - } else { + } else if (state == STATE_ACTIVE) { mTimerStart = TIMER_INACTIVE; - } - if (importance > mImportanceToKeepSessionAlive) { - setAlarmLocked(); - } else { cancelAlarmLocked(); } } @@ -318,19 +319,9 @@ public class OneTimePermissionUserManager { mIsFinished = true; cancelAlarmLocked(); try { - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove start timer listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove session killable listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mGoneListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove gone listener", e); + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); } } } @@ -394,9 +385,11 @@ public class OneTimePermissionUserManager { mPermissionControllerManager.notifyOneTimePermissionSessionTimeout( mPackageName); }); - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - mActivityManager.removeOnUidImportanceListener(mGoneListener); + try { + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); + } synchronized (mLock) { mListeners.remove(mUid); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d648d6fc663d4..fd48cdaeebf2d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -386,8 +386,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, - long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long timeoutMillis, long revokeAfterKilledDelayMillis) { mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS @@ -397,8 +396,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long token = Binder.clearCallingIdentity(); try { getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName, - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } finally { Binder.restoreCallingIdentity(token); } From b75a5c625592e2e64d0f8ff80bfbfcd071db4f76 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 31 Jan 2023 17:14:34 -0800 Subject: [PATCH 60/74] [1-time permissions] Use internal api to check proc states We need to check the proc state and the binder method has a filter that is affected by a bug that keeps a killed a proces in the "pending top" list. Using the internal api isn't affected by this filter and also is more correct for inprocess calls. Test: Install test app that requests permission and will exit immediately on granting, observe permission is no longer indefinitely held. Bug: 254736794 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e836611f3057cf9eae589a34a39fe80d0a9145f3) Merged-In: I30579090c803b231fd750abbc4ad645805f7ece2 Change-Id: I30579090c803b231fd750abbc4ad645805f7ece2 --- .../pm/permission/OneTimePermissionUserManager.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index a1c98109052e2..d28048ce74c79 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -18,6 +18,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.IUidObserver; @@ -34,6 +35,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; /** * Class that handles one-time permissions for a user @@ -49,6 +51,7 @@ public class OneTimePermissionUserManager { private final @NonNull Context mContext; private final @NonNull IActivityManager mIActivityManager; + private final @NonNull ActivityManagerInternal mActivityManagerInternal; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -79,6 +82,7 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; mIActivityManager = ActivityManager.getService(); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); @@ -241,12 +245,7 @@ public class OneTimePermissionUserManager { } private int getCurrentState() { - try { - return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null)); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Couldn't check uid proc state", e); - } - return STATE_GONE; + return getStateFromProcState(mActivityManagerInternal.getUidProcessState(mUid)); } private int getStateFromProcState(int procState) { From ee3f2dddcc6bad5f66399320964098bbe9889e84 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 21 Apr 2023 15:39:22 +0000 Subject: [PATCH 61/74] Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added the person URIs in the test, since they weren't being checked. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:43b1711332763788c7abf05c3baa931296c45bbb) Merged-In: I848545f7aee202495c515f47a32871a2cb6ae707 Change-Id: I848545f7aee202495c515f47a32871a2cb6ae707 --- core/java/android/app/Notification.java | 11 +++++++ .../NotificationManagerServiceTest.java | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index eb9567c26d8df..de8fb509bde93 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2853,6 +2853,17 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { 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 6827bbe1b43bd..8f8b1c50891ac 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -128,6 +128,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -5373,10 +5374,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -5388,6 +5415,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); } @Test From 56e9c33cc91f461c79ce8ffe9330f96f21e16216 Mon Sep 17 00:00:00 2001 From: Sumedh Sen Date: Thu, 23 Mar 2023 16:29:47 -0700 Subject: [PATCH 62/74] [RESTRICT AUTOMERGE] Prevent installing apps in policy restricted work profile using ADB If DISALLOW_DEBUGGING_FEATURES or DISALLOW_INSTALL_APPS restrictions are set on a work profile, prevent side loading of APKs using ADB in the work profile. Bug: 257443065 Test: atest CtsPackageInstallTestCases:UserRestrictionInstallTest (cherry picked from commit febe3918020a94b2af48ade98eb6a49cdd4a3bdf) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b988a09db551d9a8b2aeb0e8eb88e610605709e8) Merged-In: I169a1f72c84528ca606b6a4da165d4fbcd02b08d Change-Id: I169a1f72c84528ca606b6a4da165d4fbcd02b08d --- .../server/pm/InstallPackageHelper.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c32a57c68ede3..259701166147f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2093,9 +2093,25 @@ final class InstallPackageHelper { // The caller explicitly specified INSTALL_ALL_USERS flag. // Thus, updating the settings to install the app for all users. for (int currentUserId : allUsers) { - ps.setInstalled(true, currentUserId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, - installerPackageName); + // If the app is already installed for the currentUser, + // keep it as installed as we might be updating the app at this place. + // If not currently installed, check if the currentUser is restricted by + // DISALLOW_INSTALL_APPS or DISALLOW_DEBUGGING_FEATURES device policy. + // Install / update the app if the user isn't restricted. Skip otherwise. + final boolean installedForCurrentUser = ArrayUtils.contains( + installedForUsers, currentUserId); + final boolean restrictedByPolicy = + mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_INSTALL_APPS) + || mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_DEBUGGING_FEATURES); + if (installedForCurrentUser || !restrictedByPolicy) { + ps.setInstalled(true, currentUserId); + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId, + installerPackageName); + } else { + ps.setInstalled(false, currentUserId); + } } } From 58c80875b2487d68794784c11995532471e045a7 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 12:36:05 +0000 Subject: [PATCH 63/74] Visit URIs in landscape/portrait custom remote views. Bug: 277740848 Test: atest RemoteViewsTest NotificationManagerServiceTest & tested with POC from bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b4692946c10d11c1e935869e11dc709a9cdcba69) Merged-In: I7d3d35df0ec38945019f71755bed8797b7af4517 Change-Id: I7d3d35df0ec38945019f71755bed8797b7af4517 --- core/java/android/widget/RemoteViews.java | 6 ++ .../src/android/widget/RemoteViewsTest.java | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a339062677361..c985ae82307c2 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer visitor) { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 00b3693c902bb..a9d3ce51b2c3d 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -33,6 +37,8 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; @@ -58,6 +64,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -690,4 +697,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } From 5c4a098e52df16997a30598942af6420dcde6b60 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 18 Oct 2022 05:21:30 +0000 Subject: [PATCH 64/74] Ensure that only SysUI can override pending intent launch flags - Originally added in ag/5139951, this method ensured that activities launched from widgets are always started in a new task (if the activity is launched in the home task, the task is not brough forward with the recents transition). We can restrict this to only recents callers since this only applies to 1p launchers in gesture nav (both the gesture with 3p launchers and button nav in general will always start the home intent directly, which makes adding the NEW_TASK flag unnecessary). Bug: 243794108 Test: Ensure that the original bug b/112508020 still works (with the test app in the bug, swipe up still works after launching an activity from the widget, and fails without applying the override flags) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c4d3106e347922610f8c554de3ae238175ed393e) Merged-In: Id53c6a2aa6da5933d488ca06a0bfc4ef89a4c343 Change-Id: Id53c6a2aa6da5933d488ca06a0bfc4ef89a4c343 --- .../com/android/server/am/PendingIntentRecord.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index bda60ff2172b3..8624ee031a936 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,11 +379,16 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is to ensure that the caller - // can specify a consistent launch mode even if the PendingIntent is immutable + // Apply any launch flags from the ActivityOptions. This is used only by SystemUI + // to ensure that we can launch the pending intent with a consistent launch mode even + // if the provided PendingIntent is immutable (ie. to force an activity to launch into + // a new task, or to launch multiple instances if supported by the app) final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + // TODO(b/254490217): Move this check into SafeActivityOptions + if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + } } // Extract options before clearing calling identity From 5fd5a733434bfc58cf432d2cabfc40664210c8cf Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 11 Jan 2023 18:58:41 +0000 Subject: [PATCH 65/74] Revert "Ensure that only SysUI can override pending intent launch flags" This reverts commit c4d3106e347922610f8c554de3ae238175ed393e. Reason for revert: b/264884187, b/264885689 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48acfb0f1d71912e757cadd505901471c1df4d4c) Merged-In: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba Change-Id: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba --- .../com/android/server/am/PendingIntentRecord.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 8624ee031a936..bda60ff2172b3 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -379,16 +379,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is used only by SystemUI - // to ensure that we can launch the pending intent with a consistent launch mode even - // if the provided PendingIntent is immutable (ie. to force an activity to launch into - // a new task, or to launch multiple instances if supported by the app) + // Apply any launch flags from the ActivityOptions. This is to ensure that the caller + // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - // TODO(b/254490217): Move this check into SafeActivityOptions - if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); - } + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } // Extract options before clearing calling identity From 38c5d37d51915628e35c65689baeefe1bfe2e4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 13 Apr 2023 17:58:22 +0200 Subject: [PATCH 66/74] Grant URI permissions to the CallStyle-related ones This will also verify that the caller app can actually grant them. Fix: 274592467 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4dee5aab12e95cd8b4d663ad050f07b0f2433596) Merged-In: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc Change-Id: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc --- core/java/android/app/Notification.java | 8 +++++++ .../NotificationManagerServiceTest.java | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7f48e39a9a797..eb9567c26d8df 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2883,6 +2883,14 @@ public class Notification implements Parcelable } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } 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 874846d333cfd..6827bbe1b43bd 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5390,6 +5390,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(backgroundImage)); } + @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); From 88578ece60eb7704347c6ed49c5f5779ab042694 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 8 Feb 2023 01:04:46 +0000 Subject: [PATCH 67/74] Only allow NEW_TASK flag when adjusting pending intents Bug: 243794108 Test: atest CtsSecurityBulletinHostTestCases:android.security.cts.CVE_2023_20918 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c62d2e1021a030f4f0ae5fcfc8fe8e0875fa669f) Merged-In: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b Change-Id: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b --- core/java/android/app/ActivityOptions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d6f44e60eb0c2..2540f1dea732e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -21,6 +21,8 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; @@ -1727,7 +1729,9 @@ public class ActivityOptions extends ComponentOptions { * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** From c06fc29e702cc468e7c859f9e4c3cbf5b0baf751 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 28 Mar 2023 13:15:04 -0700 Subject: [PATCH 68/74] Dismiss keyguard when simpin auth'd and... security method is none. This is mostly to fix the case where we auth sim pin in the set up wizard and it goes straight to keyguard instead of the setup wizard activity. This works with the prevent bypass keyguard flag because the device should be noe secure in this case. Fixes: 222446076 Test: turn locked sim on, which opens the sim pin screen. Auth the screen and observe that keyguard is not shown. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48fa9bef3451e4a358c941af5b230f99881c5cb6) Cherry-picking this CL as a security fix Bug: 222446076 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:65ea56f54c059584eb27ec53d486dba8161316ab) Merged-In: Id302c41f63028bc6dd58ba686e23d73565de9675 Change-Id: Id302c41f63028bc6dd58ba686e23d73565de9675 --- .../android/keyguard/KeyguardSecurityContainerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 061bab8a7006b..16299c7aff7b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -752,7 +752,7 @@ public class KeyguardSecurityContainerController extends ViewController Date: Fri, 21 Apr 2023 09:45:07 +0000 Subject: [PATCH 69/74] Truncate ShortcutInfo Id Creating Conversation with a ShortcutId longer than 65_535 (max unsigned short), we did not save the conversation settings into the notification_policy.xml due to a restriction in FastDataOutput. This put us to a state where the user changing the importance or turning off the notifications for the given conversation had no effect on notification behavior. Fixes: 273729476 Test: atest ShortcutManagerTest2 Test: Create a test app which creates a Conversation with a long shortcutId. Go to the Conversation Settings and turn off Notifications. Post a new Notification to this Conversation and see if it is displayed. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8a5223fcd59e805da510c31c5f8462c5d01ec6d5) Merged-In: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 Change-Id: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 --- .../java/android/content/pm/ShortcutInfo.java | 20 ++++++++++++++++--- .../server/pm/ShortcutManagerTest2.java | 9 +++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 52774e354c901..c33390398400e 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -283,6 +283,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -475,8 +481,7 @@ public final class ShortcutInfo implements Parcelable { private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -582,6 +587,14 @@ public final class ShortcutInfo implements Parcelable { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2336,7 +2349,8 @@ public final class ShortcutInfo implements Parcelable { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl, android.content.ComponentName.class); mFlags = source.readInt(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index c78678431dacd..86d4655e9d3a6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -228,6 +228,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + "s".repeat(Short.MAX_VALUE)).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) From 98e98beaf8e0ef88f73b24e4a10fa2b9717d1a39 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 12 Apr 2022 17:36:41 -0700 Subject: [PATCH 70/74] Watch uid proc state instead of importance for 1-time permissions The system process may bind to an app with the flag BIND_FOREGROUND_SERVICE, this will put the client in the foreground service importance level without the normal requirement that foreground services must show a notification. Looking at proc states instead allows us to differentiate between these two levels of foreground service and revoke the client when not in use. This change makes the parameters `importanceToResetTimer` and `importanceToKeepSessionAlive` in PermissionManager#startOneTimePermissionSession obsolete. Test: atest CtsPermissionTestCases + manual testing with mic/cam/loc Bug: 217981062 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0be78fbbf7d92bf29858aa0c48b171045ab5057f) Merged-In: I7a725647c001062d1a76a82b680a02e3e2edcb03 Change-Id: I7a725647c001062d1a76a82b680a02e3e2edcb03 --- .../permission/IPermissionManager.aidl | 3 +- .../android/permission/PermissionManager.java | 3 +- .../OneTimePermissionUserManager.java | 227 +++++++++--------- .../permission/PermissionManagerService.java | 6 +- 4 files changed, 114 insertions(+), 125 deletions(-) diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 6a93b354f4dab..45dad9861406a 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -77,8 +77,7 @@ interface IPermissionManager { List getSplitPermissions(); void startOneTimePermissionSession(String packageName, int userId, long timeout, - long revokeAfterKilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive); + long revokeAfterKilledDelay); void stopOneTimePermissionSession(String packageName, int userId); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 6b540d72bba00..67699543131ae 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1371,8 +1371,7 @@ public final class PermissionManager { @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { try { mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(), - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 881f8707fdd86..a1c98109052e2 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -16,17 +16,18 @@ package com.android.server.pm.permission; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; - import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.IActivityManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Handler; +import android.os.RemoteException; import android.permission.PermissionControllerManager; import android.provider.DeviceConfig; import android.util.Log; @@ -47,7 +48,7 @@ public class OneTimePermissionUserManager { "one_time_permissions_killed_delay_millis"; private final @NonNull Context mContext; - private final @NonNull ActivityManager mActivityManager; + private final @NonNull IActivityManager mIActivityManager; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -77,49 +78,14 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; - mActivityManager = context.getSystemService(ActivityManager.class); + mIActivityManager = ActivityManager.getService(); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); } - /** - * Starts a one-time permission session for a given package. A one-time permission session is - * ended if app becomes inactive. Inactivity is defined as the package's uid importance level - * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid - * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start - * until going > importanceToResetTimer. - *

- * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive - * then the session is extended until either the importance goes above - * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which - * will continue the session and reset the timer. - *

- *

- * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}. - *

- *

- * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout - * is invoked. - *

- *

- * Note that if there is currently an active session for a package a new one isn't created and - * the existing one isn't changed. - *

- * @param packageName The package to start a one-time permission session for - * @param timeoutMillis Number of milliseconds for an app to be in an inactive state - * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies - * before ending the session. Set to -1 to use default value - * for the device. - * @param importanceToResetTimer The least important level to uid must be to reset the timer - * @param importanceToKeepSessionAlive The least important level the uid must be to keep the - * session alive - * - * @hide - */ void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, - long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long revokeAfterKilledDelayMillis) { int uid; try { uid = mContext.getPackageManager().getPackageUid(packageName, 0); @@ -131,13 +97,11 @@ public class OneTimePermissionUserManager { synchronized (mLock) { PackageInactivityListener listener = mListeners.get(uid); if (listener != null) { - listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis, - importanceToResetTimer, importanceToKeepSessionAlive); + listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis); return; } listener = new PackageInactivityListener(uid, packageName, timeoutMillis, - revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + revokeAfterKilledDelayMillis); mListeners.put(uid, listener); } } @@ -182,34 +146,58 @@ public class OneTimePermissionUserManager { private static final long TIMER_INACTIVE = -1; + private static final int STATE_GONE = 0; + private static final int STATE_TIMER = 1; + private static final int STATE_ACTIVE = 2; + private final int mUid; private final @NonNull String mPackageName; private long mTimeout; private long mRevokeAfterKilledDelay; - private int mImportanceToResetTimer; - private int mImportanceToKeepSessionAlive; private boolean mIsAlarmSet; private boolean mIsFinished; private long mTimerStart = TIMER_INACTIVE; - private final ActivityManager.OnUidImportanceListener mStartTimerListener; - private final ActivityManager.OnUidImportanceListener mSessionKillableListener; - private final ActivityManager.OnUidImportanceListener mGoneListener; - private final Object mInnerLock = new Object(); private final Object mToken = new Object(); + private final IUidObserver.Stub mObserver = new IUidObserver.Stub() { + @Override + public void onUidGone(int uid, boolean disabled) { + if (uid == mUid) { + PackageInactivityListener.this.updateUidState(STATE_GONE); + } + } + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, + int capability) { + if (uid == mUid) { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) { + PackageInactivityListener.this.updateUidState(STATE_TIMER); + } else { + PackageInactivityListener.this.updateUidState(STATE_ACTIVE); + } + } + } + + public void onUidActive(int uid) { + } + public void onUidIdle(int uid, boolean disabled) { + } + public void onUidProcAdjChanged(int uid) { + } + public void onUidCachedChanged(int uid, boolean cached) { + } + }; private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, - long revokeAfterkilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive) { - + long revokeAfterkilledDelay) { Log.i(LOG_TAG, "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout - + " killedDelay=" + revokeAfterkilledDelay - + " importanceToResetTimer=" + importanceToResetTimer - + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); + + " killedDelay=" + revokeAfterkilledDelay); mUid = uid; mPackageName = packageName; @@ -219,27 +207,24 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterkilledDelay; - mImportanceToResetTimer = importanceToResetTimer; - mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; - mStartTimerListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mSessionKillableListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mGoneListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); + try { + mIActivityManager.registerUidObserver(mObserver, + ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + null); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); + // Can't register uid observer, just revoke immediately + synchronized (mInnerLock) { + onPackageInactiveLocked(); + } + } - mActivityManager.addOnUidImportanceListener(mStartTimerListener, - importanceToResetTimer); - mActivityManager.addOnUidImportanceListener(mSessionKillableListener, - importanceToKeepSessionAlive); - mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED); - - onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); + updateUidState(); } - public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis, - int importanceToResetTimer, int importanceToKeepSessionAlive) { + public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) { synchronized (mInnerLock) { mTimeout = Math.min(mTimeout, timeoutMillis); mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay, @@ -248,63 +233,79 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterKilledDelayMillis); - mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer); - mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive, - mImportanceToKeepSessionAlive); Log.v(LOG_TAG, "Updated params for " + mPackageName + ". timeout=" + mTimeout - + " killedDelay=" + mRevokeAfterKilledDelay - + " importanceToResetTimer=" + mImportanceToResetTimer - + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive); - onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName)); + + " killedDelay=" + mRevokeAfterKilledDelay); + updateUidState(); } } - private void onImportanceChanged(int uid, int importance) { - if (uid != mUid) { - return; + private int getCurrentState() { + try { + return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null)); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); } + return STATE_GONE; + } - Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")." - + " importance=" + importance); + private int getStateFromProcState(int procState) { + if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) { + return STATE_GONE; + } else { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + return STATE_TIMER; + } else { + return STATE_ACTIVE; + } + } + } + + private void updateUidState() { + updateUidState(getCurrentState()); + } + + private void updateUidState(int state) { + Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")." + + " state=" + state); synchronized (mInnerLock) { // Remove any pending inactivity callback mHandler.removeCallbacksAndMessages(mToken); - if (importance > IMPORTANCE_CACHED) { + if (state == STATE_GONE) { if (mRevokeAfterKilledDelay == 0) { onPackageInactiveLocked(); return; } // Delay revocation in case app is restarting mHandler.postDelayed(() -> { - int imp = mActivityManager.getUidImportance(mUid); - if (imp > IMPORTANCE_CACHED) { - onPackageInactiveLocked(); - } else { - if (DEBUG) { - Log.d(LOG_TAG, "No longer gone after delayed revocation. " - + "Rechecking for " + mPackageName + " (" + mUid + ")."); + int currentState; + synchronized (mInnerLock) { + currentState = getCurrentState(); + if (currentState == STATE_GONE) { + onPackageInactiveLocked(); + return; } - onImportanceChanged(mUid, imp); } + if (DEBUG) { + Log.d(LOG_TAG, "No longer gone after delayed revocation. " + + "Rechecking for " + mPackageName + " (" + mUid + + ")."); + } + updateUidState(currentState); }, mToken, mRevokeAfterKilledDelay); return; - } - if (importance > mImportanceToResetTimer) { + } else if (state == STATE_TIMER) { if (mTimerStart == TIMER_INACTIVE) { if (DEBUG) { Log.d(LOG_TAG, "Start the timer for " + mPackageName + " (" + mUid + ")."); } mTimerStart = System.currentTimeMillis(); + setAlarmLocked(); } - } else { + } else if (state == STATE_ACTIVE) { mTimerStart = TIMER_INACTIVE; - } - if (importance > mImportanceToKeepSessionAlive) { - setAlarmLocked(); - } else { cancelAlarmLocked(); } } @@ -318,19 +319,9 @@ public class OneTimePermissionUserManager { mIsFinished = true; cancelAlarmLocked(); try { - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove start timer listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove session killable listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mGoneListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove gone listener", e); + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); } } } @@ -394,9 +385,11 @@ public class OneTimePermissionUserManager { mPermissionControllerManager.notifyOneTimePermissionSessionTimeout( mPackageName); }); - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - mActivityManager.removeOnUidImportanceListener(mGoneListener); + try { + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); + } synchronized (mLock) { mListeners.remove(mUid); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d648d6fc663d4..fd48cdaeebf2d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -386,8 +386,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, - long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long timeoutMillis, long revokeAfterKilledDelayMillis) { mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS @@ -397,8 +396,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long token = Binder.clearCallingIdentity(); try { getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName, - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } finally { Binder.restoreCallingIdentity(token); } From 9fcbb3f8e0e7a4c36448347d873ab203d69cc8ad Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 31 Jan 2023 17:14:34 -0800 Subject: [PATCH 71/74] [1-time permissions] Use internal api to check proc states We need to check the proc state and the binder method has a filter that is affected by a bug that keeps a killed a proces in the "pending top" list. Using the internal api isn't affected by this filter and also is more correct for inprocess calls. Test: Install test app that requests permission and will exit immediately on granting, observe permission is no longer indefinitely held. Bug: 254736794 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e836611f3057cf9eae589a34a39fe80d0a9145f3) Merged-In: I30579090c803b231fd750abbc4ad645805f7ece2 Change-Id: I30579090c803b231fd750abbc4ad645805f7ece2 --- .../pm/permission/OneTimePermissionUserManager.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index a1c98109052e2..d28048ce74c79 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -18,6 +18,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.IUidObserver; @@ -34,6 +35,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; /** * Class that handles one-time permissions for a user @@ -49,6 +51,7 @@ public class OneTimePermissionUserManager { private final @NonNull Context mContext; private final @NonNull IActivityManager mIActivityManager; + private final @NonNull ActivityManagerInternal mActivityManagerInternal; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -79,6 +82,7 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; mIActivityManager = ActivityManager.getService(); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); @@ -241,12 +245,7 @@ public class OneTimePermissionUserManager { } private int getCurrentState() { - try { - return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null)); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Couldn't check uid proc state", e); - } - return STATE_GONE; + return getStateFromProcState(mActivityManagerInternal.getUidProcessState(mUid)); } private int getStateFromProcState(int procState) { From 23b2230428b170f68561f441986c40922de56de8 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 21 Apr 2023 15:39:22 +0000 Subject: [PATCH 72/74] Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added the person URIs in the test, since they weren't being checked. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:43b1711332763788c7abf05c3baa931296c45bbb) Merged-In: I848545f7aee202495c515f47a32871a2cb6ae707 Change-Id: I848545f7aee202495c515f47a32871a2cb6ae707 --- core/java/android/app/Notification.java | 11 +++++++ .../NotificationManagerServiceTest.java | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index eb9567c26d8df..de8fb509bde93 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2853,6 +2853,17 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { 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 6827bbe1b43bd..8f8b1c50891ac 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -128,6 +128,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -5373,10 +5374,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -5388,6 +5415,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); } @Test From 5d4da10ff8a206a15f3749fb7cc15f7d7f60482d Mon Sep 17 00:00:00 2001 From: Sumedh Sen Date: Thu, 23 Mar 2023 16:29:47 -0700 Subject: [PATCH 73/74] [RESTRICT AUTOMERGE] Prevent installing apps in policy restricted work profile using ADB If DISALLOW_DEBUGGING_FEATURES or DISALLOW_INSTALL_APPS restrictions are set on a work profile, prevent side loading of APKs using ADB in the work profile. Bug: 257443065 Test: atest CtsPackageInstallTestCases:UserRestrictionInstallTest (cherry picked from commit febe3918020a94b2af48ade98eb6a49cdd4a3bdf) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b988a09db551d9a8b2aeb0e8eb88e610605709e8) Merged-In: I169a1f72c84528ca606b6a4da165d4fbcd02b08d Change-Id: I169a1f72c84528ca606b6a4da165d4fbcd02b08d --- .../server/pm/InstallPackageHelper.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c32a57c68ede3..259701166147f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2093,9 +2093,25 @@ final class InstallPackageHelper { // The caller explicitly specified INSTALL_ALL_USERS flag. // Thus, updating the settings to install the app for all users. for (int currentUserId : allUsers) { - ps.setInstalled(true, currentUserId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, - installerPackageName); + // If the app is already installed for the currentUser, + // keep it as installed as we might be updating the app at this place. + // If not currently installed, check if the currentUser is restricted by + // DISALLOW_INSTALL_APPS or DISALLOW_DEBUGGING_FEATURES device policy. + // Install / update the app if the user isn't restricted. Skip otherwise. + final boolean installedForCurrentUser = ArrayUtils.contains( + installedForUsers, currentUserId); + final boolean restrictedByPolicy = + mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_INSTALL_APPS) + || mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_DEBUGGING_FEATURES); + if (installedForCurrentUser || !restrictedByPolicy) { + ps.setInstalled(true, currentUserId); + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId, + installerPackageName); + } else { + ps.setInstalled(false, currentUserId); + } } } From af3a78d8b6d71dad57c9c5be153f09dc01ea9321 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 12:36:05 +0000 Subject: [PATCH 74/74] Visit URIs in landscape/portrait custom remote views. Bug: 277740848 Test: atest RemoteViewsTest NotificationManagerServiceTest & tested with POC from bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b4692946c10d11c1e935869e11dc709a9cdcba69) Merged-In: I7d3d35df0ec38945019f71755bed8797b7af4517 Change-Id: I7d3d35df0ec38945019f71755bed8797b7af4517 --- core/java/android/widget/RemoteViews.java | 6 ++ .../src/android/widget/RemoteViewsTest.java | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a339062677361..c985ae82307c2 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer visitor) { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 00b3693c902bb..a9d3ce51b2c3d 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -33,6 +37,8 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; @@ -58,6 +64,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -690,4 +697,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } }