Merge "Update screenshot UI elevations" into 24D1-dev
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 68a85b4..44e312d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -656,7 +656,7 @@
}
@Test
- fun authenticateFallbacksToDetectionWhenUserIsAlreadyTrustedByTrustManager() =
+ fun authenticateFallbacksToDetectionWhenKeyguardIsAlreadyDismissible() =
testScope.runTest {
whenever(faceManager.sensorPropertiesInternal)
.thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
@@ -665,7 +665,7 @@
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- trustRepository.setCurrentUserTrusted(true)
+ keyguardRepository.setKeyguardDismissible(true)
assertThat(canFaceAuthRun()).isFalse()
underTest.requestAuthenticate(
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index fa5308d..79b82bf 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -147,4 +147,11 @@
<include layout="@layout/screenshot_work_profile_first_run" />
<include layout="@layout/screenshot_detection_notice" />
</FrameLayout>
+ <ImageView
+ android:id="@+id/screenshot_flash"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:elevation="12dp"
+ android:src="@android:color/white"/>
</com.android.systemui.screenshot.ui.ScreenshotShelfView>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ef9235b..22fefff 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1954,8 +1954,6 @@
<string name="group_system_cycle_back">Cycle backward through recent apps</string>
<!-- User visible title for the keyboard shortcut that accesses list of all apps and search. [CHAR LIMIT=70] -->
<string name="group_system_access_all_apps_search">Open apps list</string>
- <!-- User visible title for the keyboard shortcut that hides and (re)showes taskbar. [CHAR LIMIT=70] -->
- <string name="group_system_hide_reshow_taskbar">Show taskbar</string>
<!-- User visible title for the keyboard shortcut that accesses [system] settings. [CHAR LIMIT=70] -->
<string name="group_system_access_system_settings">Open settings</string>
<!-- User visible title for the keyboard shortcut that accesses Assistant app. [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8c51a4e..519622e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -42,7 +42,6 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
-import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import android.annotation.AnyThread;
import android.annotation.MainThread;
@@ -1868,7 +1867,9 @@
@Override
public void onPostureChanged(@DevicePostureInt int posture) {
if (posture == DEVICE_POSTURE_OPENED) {
- mLogger.d("Posture changed to open - attempting to request active unlock");
+ mLogger.d("Posture changed to open - attempting to request active"
+ + " unlock and run face auth");
+ getFaceAuthInteractor().onDeviceUnfolded();
requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE,
false);
}
@@ -2434,9 +2435,7 @@
updateFingerprintListeningState(BIOMETRIC_ACTION_START);
}
});
- if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
- mDevicePostureController.addCallback(mPostureCallback);
- }
+ mDevicePostureController.addCallback(mPostureCallback);
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
index 59b59bf..7d4ba84 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -39,6 +39,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.res.R
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -68,6 +69,8 @@
/** The info of current available camera. */
val cameraInfo: StateFlow<CameraInfo?>
+
+ val supportedPostures: List<DevicePosture>
}
/** Describes a biometric sensor */
@@ -188,6 +191,15 @@
initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0] else null
)
+ private val supportedPosture =
+ applicationContext.resources.getInteger(R.integer.config_face_auth_supported_posture)
+ override val supportedPostures: List<DevicePosture> =
+ if (supportedPosture == 0) {
+ DevicePosture.entries
+ } else {
+ listOf(DevicePosture.toPosture(supportedPosture))
+ }
+
private val defaultSensorLocation: StateFlow<Point?> =
cameraInfo
.map { it?.cameraLocation }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 1eba066..8a25e41 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -250,7 +250,10 @@
listOf(
*gatingConditionsForAuthAndDetect(),
Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
- Pair(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserIsNotTrusted"),
+ Pair(
+ keyguardRepository.isKeyguardDismissible.isFalse(),
+ "keyguardIsNotDismissible"
+ ),
Pair(
biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
"isFaceAuthCurrentlyAllowed"
@@ -273,7 +276,7 @@
Pair(
biometricSettingsRepository.isFaceAuthCurrentlyAllowed
.isFalse()
- .or(trustRepository.isCurrentUserTrusted),
+ .or(keyguardRepository.isKeyguardDismissible),
"faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
),
// We don't want to run face detect if fingerprint can be used to unlock the
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 99bd25b..2e51206 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -64,6 +64,7 @@
fun onPrimaryBouncerUserInput()
fun onAccessibilityAction()
fun onWalletLaunched()
+ fun onDeviceUnfolded()
/** Whether face auth is considered class 3 */
fun isFaceAuthStrong(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 3b94166..49d4326 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -66,4 +66,5 @@
override fun onPrimaryBouncerUserInput() {}
override fun onAccessibilityAction() {}
override fun onWalletLaunched() = Unit
+ override fun onDeviceUnfolded() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 0c9fbc2..78f9a74 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -37,6 +37,7 @@
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -244,6 +245,12 @@
}
}
+ override fun onDeviceUnfolded() {
+ if (facePropertyRepository.supportedPostures.contains(DevicePosture.OPENED)) {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_POSTURE_CHANGED, true)
+ }
+ }
+
override fun registerListener(listener: FaceAuthenticationListener) {
listeners.add(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1428e7e..3207db9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -519,7 +519,7 @@
removeWindow();
releaseMediaPlayer();
releaseContext();
- mBgExecutor.shutdownNow();
+ mBgExecutor.shutdown();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 254c133..12a3daa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -31,6 +31,7 @@
import android.view.WindowManager
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.core.animation.doOnEnd
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
@@ -77,7 +78,13 @@
private val animationController = ScreenshotAnimationController(view)
init {
- ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context))
+ ScreenshotShelfViewBinder.bind(
+ view,
+ viewModel,
+ LayoutInflater.from(context),
+ onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
+ onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }
+ )
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" }
@@ -103,7 +110,10 @@
override fun updateOrientation(insets: WindowInsets) {}
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
- return animationController.getEntranceAnimation()
+ val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
+ // reset the timeout when animation finishes
+ entrance.doOnEnd { callbacks?.onUserInteraction() }
+ return entrance
}
override fun addQuickShareChip(quickShareAction: Notification.Action) {}
@@ -111,6 +121,10 @@
override fun setChipIntents(imageData: SavedImageData) {}
override fun requestDismissal(event: ScreenshotEvent?) {
+ requestDismissal(event, null)
+ }
+
+ private fun requestDismissal(event: ScreenshotEvent?, velocity: Float?) {
debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }
// If we're already animating out, don't restart the animation
@@ -119,7 +133,7 @@
return
}
event?.let { logger.log(it, 0, packageName) }
- val animator = animationController.getExitAnimation()
+ val animator = animationController.getSwipeDismissAnimation(velocity)
animator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index 2c17873..3f4f74b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -17,43 +17,103 @@
package com.android.systemui.screenshot.ui
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
import android.animation.ValueAnimator
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.MathUtils
import android.view.View
+import android.view.animation.AnimationUtils
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.res.R
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.sign
-class ScreenshotAnimationController(private val view: View) {
+class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
private var animator: Animator? = null
-
- fun getEntranceAnimation(): Animator {
- val animator = ValueAnimator.ofFloat(0f, 1f)
- animator.addUpdateListener { view.alpha = it.animatedFraction }
- animator.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animator: Animator) {
- view.alpha = 0f
- }
- override fun onAnimationEnd(animator: Animator) {
- view.alpha = 1f
- }
- }
+ private val screenshotPreview = view.requireViewById<View>(R.id.screenshot_preview)
+ private val flashView = view.requireViewById<View>(R.id.screenshot_flash)
+ private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)
+ private val fastOutSlowIn =
+ AnimationUtils.loadInterpolator(view.context, android.R.interpolator.fast_out_slow_in)
+ private val staticUI =
+ listOf<View>(
+ view.requireViewById(R.id.screenshot_preview_border),
+ view.requireViewById(R.id.actions_container_background),
+ view.requireViewById(R.id.screenshot_badge),
+ view.requireViewById(R.id.screenshot_dismiss_button)
)
+
+ fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator {
+ val entranceAnimation = AnimatorSet()
+
+ val previewAnimator = getPreviewAnimator(bounds)
+
+ if (showFlash) {
+ val flashInAnimator =
+ ObjectAnimator.ofFloat(flashView, "alpha", 0f, 1f).apply {
+ duration = FLASH_IN_DURATION_MS
+ interpolator = fastOutSlowIn
+ }
+ val flashOutAnimator =
+ ObjectAnimator.ofFloat(flashView, "alpha", 1f, 0f).apply {
+ duration = FLASH_OUT_DURATION_MS
+ interpolator = fastOutSlowIn
+ }
+ flashInAnimator.doOnStart { flashView.visibility = View.VISIBLE }
+ flashOutAnimator.doOnEnd { flashView.visibility = View.GONE }
+ entranceAnimation.play(flashOutAnimator).after(flashInAnimator)
+ entranceAnimation.play(previewAnimator).with(flashOutAnimator)
+ entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE }
+ }
+
+ val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f)
+ fadeInAnimator.addUpdateListener {
+ for (child in staticUI) {
+ child.alpha = it.animatedValue as Float
+ }
+ }
+ entranceAnimation.play(fadeInAnimator).after(previewAnimator)
+ entranceAnimation.doOnStart {
+ for (child in staticUI) {
+ child.alpha = 0f
+ }
+ }
+
+ this.animator = entranceAnimation
+ return entranceAnimation
+ }
+
+ fun getSwipeReturnAnimation(): Animator {
+ animator?.cancel()
+ val animator = ValueAnimator.ofFloat(view.translationX, 0f)
+ animator.addUpdateListener { view.translationX = it.animatedValue as Float }
this.animator = animator
return animator
}
- fun getExitAnimation(): Animator {
- val animator = ValueAnimator.ofFloat(1f, 0f)
- animator.addUpdateListener { view.alpha = it.animatedValue as Float }
- animator.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animator: Animator) {
- view.alpha = 1f
- }
- override fun onAnimationEnd(animator: Animator) {
- view.alpha = 0f
- }
+ fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator {
+ val velocity = getAdjustedVelocity(requestedVelocity)
+ val screenWidth = view.resources.displayMetrics.widthPixels
+ // translation at which point the visible UI is fully off the screen (in the direction
+ // according to velocity)
+ val endX =
+ if (velocity < 0) {
+ -1f * actionContainer.right
+ } else {
+ (screenWidth - actionContainer.left).toFloat()
}
- )
+ val distance = endX - view.translationX
+ val animator = ValueAnimator.ofFloat(view.translationX, endX)
+ animator.addUpdateListener {
+ view.translationX = it.animatedValue as Float
+ view.alpha = 1f - it.animatedFraction
+ }
+ animator.duration = ((abs(distance / velocity))).toLong()
+
this.animator = animator
return animator
}
@@ -61,4 +121,60 @@
fun cancel() {
animator?.cancel()
}
+
+ private fun getPreviewAnimator(bounds: Rect): Animator {
+ val targetPosition = Rect()
+ screenshotPreview.getHitRect(targetPosition)
+ val startXScale = bounds.width() / targetPosition.width().toFloat()
+ val startYScale = bounds.height() / targetPosition.height().toFloat()
+ val startPos = PointF(bounds.exactCenterX(), bounds.exactCenterY())
+ val endPos = PointF(targetPosition.exactCenterX(), targetPosition.exactCenterY())
+
+ val previewYAnimator =
+ ValueAnimator.ofFloat(startPos.y, endPos.y).apply {
+ duration = PREVIEW_Y_ANIMATION_DURATION_MS
+ interpolator = fastOutSlowIn
+ }
+ previewYAnimator.addUpdateListener {
+ val progress = it.animatedValue as Float
+ screenshotPreview.y = progress - screenshotPreview.height / 2f
+ }
+ // scale animation starts/finishes at the same time as x placement
+ val previewXAndScaleAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = PREVIEW_X_ANIMATION_DURATION_MS
+ interpolator = fastOutSlowIn
+ }
+ previewXAndScaleAnimator.addUpdateListener {
+ val t = it.animatedFraction
+ screenshotPreview.scaleX = MathUtils.lerp(startXScale, 1f, t)
+ screenshotPreview.scaleY = MathUtils.lerp(startYScale, 1f, t)
+ screenshotPreview.x =
+ MathUtils.lerp(startPos.x, endPos.x, t) - screenshotPreview.width / 2f
+ }
+
+ val previewAnimator = AnimatorSet()
+ previewAnimator.play(previewXAndScaleAnimator).with(previewYAnimator)
+
+ previewAnimator.doOnStart { screenshotPreview.visibility = View.VISIBLE }
+ return previewAnimator
+ }
+
+ private fun getAdjustedVelocity(requestedVelocity: Float?): Float {
+ return if (requestedVelocity == null) {
+ val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
+ // dismiss to the left in LTR locales, to the right in RTL
+ if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY
+ } else {
+ sign(requestedVelocity) * max(MINIMUM_VELOCITY, abs(requestedVelocity))
+ }
+ }
+
+ companion object {
+ private const val MINIMUM_VELOCITY = 1.5f // pixels per second
+ private const val FLASH_IN_DURATION_MS: Long = 133
+ private const val FLASH_OUT_DURATION_MS: Long = 217
+ private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234
+ private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index b7a03ef..f9af4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -21,6 +21,7 @@
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
+import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
@@ -30,6 +31,7 @@
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
ConstraintLayout(context, attrs) {
lateinit var screenshotPreview: ImageView
+ var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null
private val displayMetrics = context.resources.displayMetrics
private val tmpRect = Rect()
@@ -38,6 +40,8 @@
override fun onFinishInflate() {
super.onFinishInflate()
+ // Get focus so that the key events go to the layout.
+ isFocusableInTouchMode = true
screenshotPreview = requireViewById(R.id.screenshot_preview)
actionsContainerBackground = requireViewById(R.id.actions_container_background)
dismissButton = requireViewById(R.id.screenshot_dismiss_button)
@@ -83,4 +87,11 @@
companion object {
private const val TOUCH_PADDING_DP = 12f
}
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (onTouchInterceptListener?.invoke(ev) == true) {
+ return true
+ }
+ return super.onInterceptTouchEvent(ev)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
new file mode 100644
index 0000000..61d4489
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.screenshot.ui
+
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import com.android.systemui.screenshot.FloatingWindowUtil
+import kotlin.math.abs
+
+class SwipeGestureListener(
+ private val view: View,
+ private val onDismiss: (Float?) -> Unit,
+ private val onCancel: () -> Unit
+) {
+ private val velocityTracker = VelocityTracker.obtain()
+ private val displayMetrics = view.resources.displayMetrics
+
+ private var startX = 0f
+
+ fun onMotionEvent(ev: MotionEvent): Boolean {
+ ev.offsetLocation(view.translationX, 0f)
+ when (ev.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ velocityTracker.addMovement(ev)
+ startX = ev.rawX
+ }
+ MotionEvent.ACTION_UP -> {
+ velocityTracker.computeCurrentVelocity(1)
+ val xVelocity = velocityTracker.xVelocity
+ if (
+ abs(xVelocity) > FloatingWindowUtil.dpToPx(displayMetrics, FLING_THRESHOLD_DP)
+ ) {
+ onDismiss.invoke(xVelocity)
+ return true
+ } else if (
+ abs(view.translationX) >
+ FloatingWindowUtil.dpToPx(displayMetrics, DISMISS_THRESHOLD_DP)
+ ) {
+ onDismiss.invoke(xVelocity)
+ return true
+ } else {
+ velocityTracker.clear()
+ onCancel.invoke()
+ }
+ }
+ MotionEvent.ACTION_MOVE -> {
+ velocityTracker.addMovement(ev)
+ view.translationX = ev.rawX - startX
+ }
+ }
+ return false
+ }
+
+ companion object {
+ private const val DISMISS_THRESHOLD_DP = 80f
+ private const val FLING_THRESHOLD_DP = .8f // dp per ms
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 5f835b3..3376b8c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -18,7 +18,6 @@
import android.view.LayoutInflater
import android.view.View
-import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.lifecycle.Lifecycle
@@ -26,24 +25,44 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import com.android.systemui.screenshot.ScreenshotEvent
+import com.android.systemui.screenshot.ui.ScreenshotShelfView
+import com.android.systemui.screenshot.ui.SwipeGestureListener
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.util.children
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object ScreenshotShelfViewBinder {
fun bind(
- view: ViewGroup,
+ view: ScreenshotShelfView,
viewModel: ScreenshotViewModel,
layoutInflater: LayoutInflater,
+ onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit,
+ onDismissalCancelled: () -> Unit,
) {
+ val swipeGestureListener =
+ SwipeGestureListener(
+ view,
+ onDismiss = {
+ onDismissalRequested(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it)
+ },
+ onCancel = onDismissalCancelled
+ )
+ view.onTouchInterceptListener = { swipeGestureListener.onMotionEvent(it) }
+
val previewView: ImageView = view.requireViewById(R.id.screenshot_preview)
val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border)
previewView.clipToOutline = true
val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
- view.requireViewById<View>(R.id.screenshot_dismiss_button).visibility =
- if (viewModel.showDismissButton) View.VISIBLE else View.GONE
+ val dismissButton = view.requireViewById<View>(R.id.screenshot_dismiss_button)
+ dismissButton.visibility = if (viewModel.showDismissButton) View.VISIBLE else View.GONE
+ dismissButton.setOnClickListener {
+ onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null)
+ }
- view.repeatWhenAttached {
+ // use immediate dispatcher to ensure screenshot bitmap is set before animation
+ view.repeatWhenAttached(Dispatchers.Main.immediate) {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
@@ -79,9 +98,9 @@
// ID is unique.
val newIds = visibleActions.map { it.id }
- for (view in actionsContainer.children.toList()) {
- if (view.tag !in newIds) {
- actionsContainer.removeView(view)
+ for (child in actionsContainer.children.toList()) {
+ if (child.tag !in newIds) {
+ actionsContainer.removeView(child)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index ef4e530..f7a15fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -530,11 +530,6 @@
Pair.create(
KeyEvent.KEYCODE_TAB,
KeyEvent.META_SHIFT_ON | KeyEvent.META_ALT_ON))),
- /* Hide and (re)show taskbar: Meta + T */
- new ShortcutKeyGroupMultiMappingInfo(
- context.getString(R.string.group_system_hide_reshow_taskbar),
- Arrays.asList(
- Pair.create(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON))),
/* Access notification shade: Meta + N */
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_access_notification_shade),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 336a97e..a2d06c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2013,6 +2013,16 @@
}
@Test
+ public void unfoldFromPostureChange_sendActionToFaceAuthInteractor() {
+ // WHEN device posture changes to unfold
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+
+ // THEN request face auth
+ verify(mFaceAuthInteractor).onDeviceUnfolded();
+ }
+
+ @Test
public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() {
ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 68ef555..ea06963 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -19,6 +19,7 @@
import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
+import com.android.systemui.keyguard.shared.model.DevicePosture
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -37,6 +38,8 @@
override val cameraInfo: StateFlow<CameraInfo?>
get() = currentCameraInfo
+ override val supportedPostures: List<DevicePosture> = listOf(DevicePosture.CLOSED)
+
fun setLockoutMode(userId: Int, mode: LockoutMode) {
lockoutModesForUser[userId] = mode
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e171064..2d1aba4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1132,20 +1132,17 @@
return;
}
- int phoneId = -1;
int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- if(Flags.preventSystemServerAndPhoneDeadlock()) {
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- if (DBG) {
- log("invalid subscription id, use default id");
- }
- } else { //APP specify subID
- subscriptionId = subId;
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
}
- phoneId = getPhoneIdFromSubId(subscriptionId);
+ } else { //APP specify subID
+ subscriptionId = subId;
}
+ int phoneId = getPhoneIdFromSubId(subscriptionId);
synchronized (mRecords) {
// register
@@ -1166,23 +1163,8 @@
r.renounceFineLocationAccess = renounceFineLocationAccess;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
-
- if(!Flags.preventSystemServerAndPhoneDeadlock()) {
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- if (DBG) {
- log("invalid subscription id, use default id");
- }
- r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- } else {//APP specify subID
- r.subId = subId;
- }
- r.phoneId = getPhoneIdFromSubId(r.subId);
- } else {
- r.subId = subscriptionId;
- r.phoneId = phoneId;
- }
+ r.subId = subscriptionId;
+ r.phoneId = phoneId;
r.eventList = events;
if (DBG) {
@@ -1928,14 +1910,8 @@
}
private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
- int phoneId = -1;
- if(Flags.preventSystemServerAndPhoneDeadlock()) {
- phoneId = getPhoneIdFromSubId(subId);
- }
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
- if(!Flags.preventSystemServerAndPhoneDeadlock()) {
- phoneId = getPhoneIdFromSubId(subId);
- }
mCarrierNetworkChangeState[phoneId] = active;
if (VDBG) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 38fa79f..e2c4b46 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -88,6 +88,10 @@
/*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
{
append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+ // Speaker safe is considered compatible with spatial audio because routing media usage
+ // to speaker safe only happens in transient situations and should not affect app
+ // decisions to play spatial audio content.
+ append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE, Spatialization.Mode.TRANSAURAL);
append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
// assumption for A2DP: mostly headsets
@@ -805,7 +809,7 @@
private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
// modeForDevice will be neither transaural or binaural for devices that do not support
- // spatial audio. For instance mono devices like earpiece, speaker safe or sco must
+ // spatial audio. For instance mono devices like earpiece or sco must
// not be included.
final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ -1);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a6954ee..069a674 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3495,13 +3495,6 @@
return true;
}
break;
- case KeyEvent.KEYCODE_T:
- if (firstDown && event.isMetaPressed()) {
- toggleTaskbar();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_TASKBAR);
- return true;
- }
- break;
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_ESCAPE:
if (firstDown && event.isMetaPressed()) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c1221a6..8224958 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8634,15 +8634,14 @@
if (mDisplayContent == null) {
return;
}
- final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
int rotation = newParentConfiguration.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds
- || getCompatDisplayInsets() != null
- || isFloating(parentWindowingMode) || parentAppBounds == null
- || parentAppBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+ || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
+ || rotation == ROTATION_UNDEFINED) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
@@ -8661,7 +8660,7 @@
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentAppBounds);
+ inOutConfig.windowConfiguration.setAppBounds(parentBounds);
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
outAppBounds.inset(nonDecorInsets);
}
@@ -8672,14 +8671,11 @@
density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- inOutConfig.screenWidthDp =
- Math.min(overrideScreenWidthDp, newParentConfiguration.screenWidthDp);
+ inOutConfig.screenWidthDp = overrideScreenWidthDp;
}
if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- final int overrideScreenHeightDp =
- (int) (outAppBounds.height() / density + 0.5f);
- inOutConfig.screenHeightDp =
- Math.min(overrideScreenHeightDp, newParentConfiguration.screenHeightDp);
+ final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+ inOutConfig.screenHeightDp = overrideScreenHeightDp;
}
if (inOutConfig.smallestScreenWidthDp
== Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 0a29dfb..60716cb 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -95,8 +95,6 @@
new int[]{KeyEvent.KEYCODE_NOTIFICATION},
KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
0},
- {"Meta + T -> Toggle Taskbar", new int[]{META_KEY, KeyEvent.KEYCODE_T},
- KeyboardLogEvent.TOGGLE_TASKBAR, KeyEvent.KEYCODE_T, META_ON},
{"Meta + Ctrl + S -> Take Screenshot",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},