Merge "Fix getOrientation from adjacent TaskFragments" into tm-qpr-dev
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 9fab3a1..25eddf8 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -66,8 +66,10 @@
<!-- Multi-Window strings -->
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split-screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="dock_forced_resizable">App may not work with split-screen.</string>
- <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead. -->
+ <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead [CHAR LIMIT=NONE] -->
<string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
+ <!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
+ <string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window.</string>
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="forced_resizable_secondary_display">App may not work on a secondary display.</string>
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 1474754..e8b0f02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -76,6 +76,7 @@
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
private boolean mSetTouchRegion = true;
+ private int mLastDraggingPosition;
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -298,6 +299,7 @@
}
if (mMoving) {
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ mLastDraggingPosition = position;
mSplitLayout.updateDivideBounds(position);
}
break;
@@ -372,6 +374,15 @@
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
from);
mInteractive = interactive;
+ if (!mInteractive && mMoving) {
+ final int position = mSplitLayout.getDividePosition();
+ mSplitLayout.flingDividePosition(
+ mLastDraggingPosition,
+ position,
+ mSplitLayout.FLING_RESIZE_DURATION,
+ () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+ mMoving = false;
+ }
releaseTouching();
mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index ec9e6f7..ae49616 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -81,7 +81,7 @@
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
- private static final int FLING_RESIZE_DURATION = 250;
+ public static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_SWITCH_DURATION = 350;
private static final int FLING_ENTER_DURATION = 450;
private static final int FLING_EXIT_DURATION = 450;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 1774dd5..a79ac45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -58,6 +58,7 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
+import android.widget.Toast;
import android.window.RemoteTransition;
import android.window.WindowContainerTransaction;
@@ -597,6 +598,10 @@
} else if (isSplitScreenVisible()) {
mStageCoordinator.switchSplitPosition("startIntent");
return;
+ } else {
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
}
}
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index b24ce12..6c7cab5 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,6 +24,7 @@
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
<!-- QS-->
<dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9ed62cb..3e7b0f1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1321,6 +1321,9 @@
<!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
<string name="qr_code_scanner_title">QR code scanner</string>
+ <!-- QR Code Scanner Secondary label when GMS Core is Updating -->
+ <string name="qr_code_scanner_updating_secondary_label">Updating</string>
+
<!-- Name of the work status bar icon. -->
<string name="status_bar_work">Work profile</string>
@@ -2679,4 +2682,50 @@
<!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
<string name="dream_time_complication_24_hr_time_format">kk:mm</string>
+
+ <!--
+ Template for an action that opens a specific app. [CHAR LIMIT=16]
+ -->
+ <string name="keyguard_affordance_enablement_dialog_action_template">Open <xliff:g id="appName" example="Wallet">%1$s</xliff:g></string>
+
+ <!--
+ Template for a message shown right before a list of instructions that tell the user what to do
+ in order to enable a shortcut to a specific app. [CHAR LIMIT=NONE]
+ -->
+ <string name="keyguard_affordance_enablement_dialog_message">To add the <xliff:g id="appName" example="Wallet">%1$s</xliff:g> app as a shortcut, make sure</string>
+
+ <!--
+ Requirement for the wallet app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_wallet_instruction_1">• The app is set up</string>
+
+ <!--
+ Requirement for the wallet app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_wallet_instruction_2">• At least one card has been added to Wallet</string>
+
+ <!--
+ Requirement for the QR code scanner functionality to be available for the user to use. This is
+ shown as part of a bulleted list of requirements. When all requirements are met, the piece of
+ functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction">• Install a camera app</string>
+
+ <!--
+ Requirement for the home app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_home_instruction_1">• The app is set up</string>
+
+ <!--
+ Requirement for the home app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_home_instruction_2">• At least one device is available</string>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
index 71469a3..98d8d3e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -67,6 +67,8 @@
object AffordanceTable {
const val TABLE_NAME = "affordances"
val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+ const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
+ const val COMPONENT_NAME_SEPARATOR = "/"
object Columns {
/** String. Unique ID for this affordance. */
@@ -78,6 +80,25 @@
* ID from the system UI package.
*/
const val ICON = "icon"
+ /** Integer. `1` if the affordance is enabled or `0` if it disabled. */
+ const val IS_ENABLED = "is_enabled"
+ /**
+ * String. List of strings, delimited by [ENABLEMENT_INSTRUCTIONS_DELIMITER] to be shown
+ * to the user if the affordance is disabled and the user selects the affordance. The
+ * first one is a title while the rest are the steps needed to re-enable the affordance.
+ */
+ const val ENABLEMENT_INSTRUCTIONS = "enablement_instructions"
+ /**
+ * String. Optional label for a button that, when clicked, opens a destination activity
+ * where the user can re-enable the disabled affordance.
+ */
+ const val ENABLEMENT_ACTION_TEXT = "enablement_action_text"
+ /**
+ * String. Optional package name and activity action string, delimited by
+ * [COMPONENT_NAME_SEPARATOR] to use with an `Intent` to start an activity that opens a
+ * destination where the user can re-enable the disabled affordance.
+ */
+ const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent"
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
index b2658c9..a5b62b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -29,5 +29,4 @@
*/
public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG);
public static final boolean DEBUG_SIM_STATES = true;
- public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 88477aaa..331497e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -845,6 +845,7 @@
+ " triggered while waiting for cancellation, removing watchdog");
mHandler.removeCallbacks(mFpCancelNotReceived);
}
+ mLogger.d("handleFingerprintAuthFailed");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -974,6 +975,7 @@
stopListeningForFace(FACE_AUTH_STOPPED_FP_LOCKED_OUT);
}
+ mLogger.logFingerprintError(msgId, errString);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -3900,6 +3902,8 @@
pw.println(" listening: actual=" + mFaceRunningState
+ " expected=(" + (shouldListenForFace() ? 1 : 0));
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+ pw.println(" isNonStrongBiometricAllowedAfterIdleTimeout="
+ + mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(userId));
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 6264ce7..2bb75aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,7 +17,7 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.dagger.BiometricMessagesLog
+import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
@@ -26,7 +26,7 @@
@SysUISingleton
class FaceMessageDeferralLogger
@Inject
-constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) :
+constructor(@BiometricLog private val logBuffer: LogBuffer) :
BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger")
open class BiometricMessageDeferralLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
new file mode 100644
index 0000000..bc0bd8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.BiometricLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "BiometricUnlockLogger"
+
+/** Helper class for logging for [com.android.systemui.statusbar.phone.BiometricUnlockController] */
+@SysUISingleton
+class BiometricUnlockLogger @Inject constructor(@BiometricLog private val logBuffer: LogBuffer) {
+ fun i(@CompileTimeConstant msg: String) = log(msg, INFO)
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+ fun logStartWakeAndUnlock(mode: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { int1 = mode },
+ { "startWakeAndUnlock(${wakeAndUnlockModeToString(int1)})" }
+ )
+ }
+
+ fun logUdfpsAttemptThresholdMet(consecutiveFailedAttempts: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { int1 = consecutiveFailedAttempts },
+ { "udfpsAttemptThresholdMet consecutiveFailedAttempts=$int1" }
+ )
+ }
+
+ fun logCalculateModeForFingerprintUnlockingAllowed(
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean,
+ deviceDreaming: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = deviceInteractive
+ bool2 = keyguardShowing
+ bool3 = deviceDreaming
+ },
+ {
+ "calculateModeForFingerprint unlockingAllowed=true" +
+ " deviceInteractive=$bool1 isKeyguardShowing=$bool2" +
+ " deviceDreaming=$bool3"
+ }
+ )
+ }
+
+ fun logCalculateModeForFingerprintUnlockingNotAllowed(
+ strongBiometric: Boolean,
+ strongAuthFlags: Int,
+ nonStrongBiometricAllowed: Boolean,
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = strongAuthFlags
+ bool1 = strongBiometric
+ bool2 = nonStrongBiometricAllowed
+ bool3 = deviceInteractive
+ bool4 = keyguardShowing
+ },
+ {
+ "calculateModeForFingerprint unlockingAllowed=false" +
+ " strongBiometric=$bool1 strongAuthFlags=$int1" +
+ " nonStrongBiometricAllowed=$bool2" +
+ " deviceInteractive=$bool3 isKeyguardShowing=$bool4"
+ }
+ )
+ }
+
+ fun logCalculateModeForPassiveAuthUnlockingAllowed(
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean,
+ deviceDreaming: Boolean,
+ bypass: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = deviceInteractive
+ bool2 = keyguardShowing
+ bool3 = deviceDreaming
+ bool4 = bypass
+ },
+ {
+ "calculateModeForPassiveAuth unlockingAllowed=true" +
+ " deviceInteractive=$bool1 isKeyguardShowing=$bool2" +
+ " deviceDreaming=$bool3 bypass=$bool4"
+ }
+ )
+ }
+
+ fun logCalculateModeForPassiveAuthUnlockingNotAllowed(
+ strongBiometric: Boolean,
+ strongAuthFlags: Int,
+ nonStrongBiometricAllowed: Boolean,
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean,
+ bypass: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = if (strongBiometric) 1 else 0
+ int2 = strongAuthFlags
+ bool1 = nonStrongBiometricAllowed
+ bool2 = deviceInteractive
+ bool3 = keyguardShowing
+ bool4 = bypass
+ },
+ {
+ "calculateModeForPassiveAuth unlockingAllowed=false" +
+ " strongBiometric=${int1 == 1}" +
+ " strongAuthFlags=$int2 nonStrongBiometricAllowed=$bool1" +
+ " deviceInteractive=$bool2 isKeyguardShowing=$bool3 bypass=$bool4"
+ }
+ )
+ }
+}
+
+private fun wakeAndUnlockModeToString(mode: Int): String {
+ return when (mode) {
+ MODE_NONE -> "MODE_NONE"
+ MODE_WAKE_AND_UNLOCK -> "MODE_WAKE_AND_UNLOCK"
+ MODE_WAKE_AND_UNLOCK_PULSING -> "MODE_WAKE_AND_UNLOCK_PULSING"
+ MODE_SHOW_BOUNCER -> "MODE_SHOW_BOUNCER"
+ MODE_ONLY_WAKE -> "MODE_ONLY_WAKE"
+ MODE_UNLOCK_COLLAPSING -> "MODE_UNLOCK_COLLAPSING"
+ MODE_WAKE_AND_UNLOCK_FROM_DREAM -> "MODE_WAKE_AND_UNLOCK_FROM_DREAM"
+ MODE_DISMISS_BOUNCER -> "MODE_DISMISS_BOUNCER"
+ else -> "UNKNOWN{$mode}"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 6763700..1f6441a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -161,6 +161,13 @@
}, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
}
+ fun logFingerprintError(msgId: Int, originalErrMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ str1 = originalErrMsg
+ int1 = msgId
+ }, { "Fingerprint error received: $str1 msgId= $int1" })
+ }
+
fun logInvalidSubId(subId: Int) {
logBuffer.log(TAG, INFO,
{ int1 = subId },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index bfc60c1..29febb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -31,6 +31,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
+import kotlinx.coroutines.runBlocking
class KeyguardQuickAffordanceProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@@ -118,9 +119,9 @@
sortOrder: String?,
): Cursor? {
return when (uriMatcher.match(uri)) {
- MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
+ MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
MATCH_CODE_ALL_SLOTS -> querySlots()
- MATCH_CODE_ALL_SELECTIONS -> querySelections()
+ MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
MATCH_CODE_ALL_FLAGS -> queryFlags()
else -> null
}
@@ -194,7 +195,7 @@
}
}
- private fun querySelections(): Cursor {
+ private suspend fun querySelections(): Cursor {
return MatrixCursor(
arrayOf(
Contract.SelectionTable.Columns.SLOT_ID,
@@ -219,12 +220,16 @@
}
}
- private fun queryAffordances(): Cursor {
+ private suspend fun queryAffordances(): Cursor {
return MatrixCursor(
arrayOf(
Contract.AffordanceTable.Columns.ID,
Contract.AffordanceTable.Columns.NAME,
Contract.AffordanceTable.Columns.ICON,
+ Contract.AffordanceTable.Columns.IS_ENABLED,
+ Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS,
+ Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT,
+ Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME,
)
)
.apply {
@@ -234,6 +239,12 @@
representation.id,
representation.name,
representation.iconResourceId,
+ if (representation.isEnabled) 1 else 0,
+ representation.instructions?.joinToString(
+ Contract.AffordanceTable.ENABLEMENT_INSTRUCTIONS_DELIMITER
+ ),
+ representation.actionText,
+ representation.actionComponentName,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index d6f521c..2558fab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import androidx.annotation.DrawableRes
+import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -45,7 +46,7 @@
class HomeControlsKeyguardQuickAffordanceConfig
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val component: ControlsComponent,
) : KeyguardQuickAffordanceConfig {
@@ -66,6 +67,36 @@
}
}
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ if (!component.isEnabled()) {
+ return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+
+ val currentServices =
+ component.getControlsListingController().getOrNull()?.getCurrentServices()
+ val hasFavorites =
+ component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
+ if (currentServices.isNullOrEmpty() || !hasFavorites) {
+ return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_message,
+ pickerName,
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_home_instruction_1
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_home_instruction_2
+ ),
+ ),
+ )
+ }
+
+ return KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ }
+
override fun onTriggered(
expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index fd40d1d..4477310 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,6 +21,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -41,6 +42,12 @@
val lockScreenState: Flow<LockScreenState>
/**
+ * Returns the [PickerScreenState] representing the affordance in the settings or selector
+ * experience.
+ */
+ suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default
+
+ /**
* Notifies that the affordance was clicked by the user.
*
* @param expandable An [Expandable] to use when animating dialogs or activities
@@ -49,6 +56,58 @@
fun onTriggered(expandable: Expandable?): OnTriggeredResult
/**
+ * Encapsulates the state of a quick affordance within the context of the settings or selector
+ * experience.
+ */
+ sealed class PickerScreenState {
+
+ /** The picker shows the item for selecting this affordance as it normally would. */
+ object Default : PickerScreenState()
+
+ /**
+ * The picker does not show an item for selecting this affordance as it is not supported on
+ * the device at all. For example, missing hardware requirements.
+ */
+ object UnavailableOnDevice : PickerScreenState()
+
+ /**
+ * The picker shows the item for selecting this affordance as disabled. Clicking on it will
+ * show the given instructions to the user. If [actionText] and [actionComponentName] are
+ * provided (optional) a button will be shown to open an activity to help the user complete
+ * the steps described in the instructions.
+ */
+ data class Disabled(
+ /** List of human-readable instructions for setting up the quick affordance. */
+ val instructions: List<String>,
+ /**
+ * Optional text to display on a button that the user can click to start a flow to go
+ * and set up the quick affordance and make it enabled.
+ */
+ val actionText: String? = null,
+ /**
+ * Optional component name to be able to build an `Intent` that opens an `Activity` for
+ * the user to be able to set up the quick affordance and make it enabled.
+ *
+ * This is either just an action for the `Intent` or a package name and action,
+ * separated by [Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR] for convenience, you
+ * can use the [componentName] function.
+ */
+ val actionComponentName: String? = null,
+ ) : PickerScreenState() {
+ init {
+ check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
+ check(
+ (actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) ||
+ (!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty())
+ ) {
+ "actionText and actionComponentName must either both be null/empty or both be" +
+ " non-empty!"
+ }
+ }
+ }
+ }
+
+ /**
* Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
* button on the lock-screen).
*/
@@ -83,4 +142,18 @@
val canShowWhileLocked: Boolean,
) : OnTriggeredResult()
}
+
+ companion object {
+ fun componentName(
+ packageName: String? = null,
+ action: String?,
+ ): String? {
+ return when {
+ action.isNullOrEmpty() -> null
+ !packageName.isNullOrEmpty() ->
+ "$packageName${Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR}$action"
+ else -> action
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 11f72ff..a96ce77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -36,7 +36,7 @@
class QrCodeScannerKeyguardQuickAffordanceConfig
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
@@ -75,6 +75,28 @@
}
}
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return when {
+ !controller.isAvailableOnDevice ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ !controller.isAbleToOpenCameraApp ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_message,
+ pickerName,
+ ),
+ context.getString(
+ R.string
+ .keyguard_affordance_enablement_dialog_qr_scanner_instruction
+ ),
+ ),
+ )
+ else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 303e6a1..beb20ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -18,10 +18,12 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.Intent
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
import android.util.Log
import com.android.systemui.R
import com.android.systemui.animation.Expandable
@@ -31,25 +33,27 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.suspendCancellableCoroutine
/** Quick access wallet quick affordance data source. */
@SysUISingleton
class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- override val pickerName = context.getString(R.string.accessibility_wallet_button)
+ override val pickerName: String = context.getString(R.string.accessibility_wallet_button)
override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
@@ -58,10 +62,11 @@
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
isFeatureEnabled = walletController.isWalletEnabled,
- hasCard = response?.walletCards?.isNotEmpty() == true,
+ hasCard = hasCards,
tileIcon = walletController.walletClient.tileIcon,
),
TAG,
@@ -93,6 +98,44 @@
}
}
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return when {
+ !walletController.isWalletEnabled ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+ val componentName =
+ walletController.walletClient.createWalletSettingsIntent().toComponentName()
+ val actionText =
+ if (componentName != null) {
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_action_template,
+ pickerName,
+ )
+ } else {
+ null
+ }
+ KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_message,
+ pickerName,
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_wallet_instruction_1
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
+ ),
+ ),
+ actionText = actionText,
+ actionComponentName = componentName,
+ )
+ }
+ else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -104,6 +147,24 @@
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
+ private suspend fun queryCards(): List<WalletCard> {
+ return suspendCancellableCoroutine { continuation ->
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ continuation.resumeWith(
+ Result.success(response?.walletCards ?: emptyList())
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ continuation.resumeWith(Result.success(emptyList()))
+ }
+ }
+ walletController.queryWalletCards(callback)
+ }
+ }
+
private fun state(
isFeatureEnabled: Boolean,
hasCard: Boolean,
@@ -125,6 +186,14 @@
}
}
+ private fun Intent?.toComponentName(): String? {
+ if (this == null) {
+ return null
+ }
+
+ return componentName(packageName = `package`, action = action)
+ }
+
companion object {
private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 533b3ab..d300500 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -121,18 +121,32 @@
}
/**
- * Returns the list of representation objects for all known affordances, regardless of what is
- * selected. This is useful for building experiences like the picker/selector or user settings
- * so the user can see everything that can be selected in a menu.
+ * Returns the list of representation objects for all known, device-available affordances,
+ * regardless of what is selected. This is useful for building experiences like the
+ * picker/selector or user settings so the user can see everything that can be selected in a
+ * menu.
*/
- fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
- return configs.map { config ->
- KeyguardQuickAffordancePickerRepresentation(
- id = config.key,
- name = config.pickerName,
- iconResourceId = config.pickerIconResourceId,
- )
- }
+ suspend fun getAffordancePickerRepresentations():
+ List<KeyguardQuickAffordancePickerRepresentation> {
+ return configs
+ .associateWith { config -> config.getPickerScreenState() }
+ .filterNot { (_, pickerState) ->
+ pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ .map { (config, pickerState) ->
+ val disabledPickerState =
+ pickerState as? KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config.key,
+ name = config.pickerName,
+ iconResourceId = config.pickerIconResourceId,
+ isEnabled =
+ pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
+ instructions = disabledPickerState?.instructions,
+ actionText = disabledPickerState?.actionText,
+ actionComponentName = disabledPickerState?.actionComponentName,
+ )
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index c8216c5..2d94d76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -189,7 +189,7 @@
}
/** Returns affordance IDs indexed by slot ID, for all known slots. */
- fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
+ suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -310,7 +310,8 @@
return Pair(splitUp[0], splitUp[1])
}
- fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ suspend fun getAffordancePickerRepresentations():
+ List<KeyguardQuickAffordancePickerRepresentation> {
check(isUsingRepository)
return repository.get().getAffordancePickerRepresentations()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 3b31dcf..84a8074 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -21,6 +21,7 @@
import android.os.Trace
import android.os.UserHandle
import android.os.UserManager
+import android.view.View
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
@@ -84,6 +85,7 @@
)
)
repository.setPrimaryShowingSoon(false)
+ primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
@@ -182,6 +184,7 @@
repository.setPrimaryVisible(false)
repository.setPrimaryHide(true)
repository.setPrimaryShow(null)
+ primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
Trace.endSection()
}
@@ -276,11 +279,6 @@
repository.setShowMessage(null)
}
- /** Notify that view visibility has changed. */
- fun notifyBouncerVisibilityHasChanged(visibility: Int) {
- primaryBouncerCallbackInteractor.dispatchVisibilityChanged(visibility)
- }
-
/** Notify that the resources have been updated */
fun notifyUpdatedResources() {
repository.setResourceUpdateRequests(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index a56bc90..7d13359 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -27,4 +27,22 @@
val id: String,
val name: String,
@DrawableRes val iconResourceId: Int,
+
+ /** Whether this quick affordance is enabled. */
+ val isEnabled: Boolean = true,
+
+ /** If not enabled, the list of user-visible steps to re-enable it. */
+ val instructions: List<String>? = null,
+
+ /**
+ * If not enabled, an optional label for a button that takes the user to a destination where
+ * they can re-enable it.
+ */
+ val actionText: String? = null,
+
+ /**
+ * If not enabled, an optional component name (package and action) for a button that takes the
+ * user to a destination where they can re-enable it.
+ */
+ val actionComponentName: String? = null,
)
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 3c927ee..f772b17 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
@@ -88,7 +88,7 @@
}
}
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
try {
viewModel.setBouncerViewDelegate(delegate)
launch {
@@ -152,7 +152,6 @@
val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
view.visibility = visibility
hostViewController.onBouncerVisibilityChanged(visibility)
- viewModel.notifyBouncerVisibilityHasChanged(visibility)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 503c8ba..e5d4e49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -72,10 +72,6 @@
/** Observe whether screen is turned off. */
val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
- /** Notify that view visibility has changed. */
- fun notifyBouncerVisibilityHasChanged(visibility: Int) {
- return interactor.notifyBouncerVisibilityHasChanged(visibility)
- }
/** Observe whether we want to update resources. */
fun notifyUpdateResources() {
interactor.notifyUpdatedResources()
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
rename to packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
index eeadf40..4b774d3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
@@ -29,5 +29,5 @@
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
-public @interface BiometricMessagesLog {
+public @interface BiometricLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 9adb855..74d5043 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -300,9 +300,9 @@
*/
@Provides
@SysUISingleton
- @BiometricMessagesLog
- public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) {
- return factory.create("BiometricMessagesLog", 150);
+ @BiometricLog
+ public static LogBuffer provideBiometricLogBuffer(LogBufferFactory factory) {
+ return factory.create("BiometricLog", 200);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 2c20feb..fa3f878f 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -158,14 +158,18 @@
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
*/
public boolean isEnabledForLockScreenButton() {
- return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton
- && isActivityCallable(mIntent);
+ return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
+ }
+
+ /** Returns whether the feature is available on the device. */
+ public boolean isAvailableOnDevice() {
+ return mConfigEnableLockScreenButton;
}
/**
- * Returns true if quick settings entry point for QR Code Scanner is to be enabled.
+ * Returns true if the feature can open a camera app on the device.
*/
- public boolean isEnabledForQuickSettings() {
+ public boolean isAbleToOpenCameraApp() {
return mIntent != null && isActivityCallable(mIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 376d3d8..6d50b56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -115,8 +115,12 @@
state.label = mContext.getString(R.string.qr_code_scanner_title);
state.contentDescription = state.label;
state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
- state.state = mQRCodeScannerController.isEnabledForQuickSettings() ? Tile.STATE_INACTIVE
+ state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
: Tile.STATE_UNAVAILABLE;
+ // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
+ // would go to "Unavailable" state only when GMS core is updating.
+ state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE
+ ? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3ae2545..65a21a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1160,12 +1160,21 @@
mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
}
- if (curr.getSuppressedChanges().getParent() != null) {
- mLogger.logParentChangeSuppressed(
+ GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
+ GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
+ if (currSuppressedParent != null && (prevSuppressedParent == null
+ || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) {
+ mLogger.logParentChangeSuppressedStarted(
mIterationCount,
- curr.getSuppressedChanges().getParent(),
+ currSuppressedParent,
curr.getParent());
}
+ if (prevSuppressedParent != null && currSuppressedParent == null) {
+ mLogger.logParentChangeSuppressedStopped(
+ mIterationCount,
+ prevSuppressedParent,
+ prev.getParent());
+ }
if (curr.getSuppressedChanges().getSection() != null) {
mLogger.logSectionChangeSuppressed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8e052c7..4adc90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -193,7 +193,7 @@
})
}
- fun logParentChangeSuppressed(
+ fun logParentChangeSuppressedStarted(
buildId: Int,
suppressedParent: GroupEntry?,
keepingParent: GroupEntry?
@@ -207,6 +207,21 @@
})
}
+ fun logParentChangeSuppressedStopped(
+ buildId: Int,
+ previouslySuppressedParent: GroupEntry?,
+ previouslyKeptParent: GroupEntry?
+ ) {
+ buffer.log(TAG, INFO, {
+ long1 = buildId.toLong()
+ str1 = previouslySuppressedParent?.logKey
+ str2 = previouslyKeptParent?.logKey
+ }, {
+ "(Build $long1) Change of parent to '$str1' no longer suppressed; " +
+ "replaced parent '$str2'"
+ })
+ }
+
fun logGroupPruningSuppressed(
buildId: Int,
keepingParent: GroupEntry?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 182d397..679bcea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -29,7 +29,6 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.Trace;
-import android.util.Log;
import androidx.annotation.Nullable;
@@ -41,10 +40,10 @@
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
@@ -79,9 +78,6 @@
*/
@SysUISingleton
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
-
- private static final String TAG = "BiometricUnlockCtrl";
- private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -176,6 +172,7 @@
private final StatusBarStateController mStatusBarStateController;
private final LatencyTracker mLatencyTracker;
private final VibratorHelper mVibratorHelper;
+ private final BiometricUnlockLogger mLogger;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -262,7 +259,8 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
@Inject
- public BiometricUnlockController(DozeScrimController dozeScrimController,
+ public BiometricUnlockController(
+ DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
ShadeController shadeController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -272,6 +270,7 @@
KeyguardBypassController keyguardBypassController,
MetricsLogger metricsLogger, DumpManager dumpManager,
PowerManager powerManager,
+ BiometricUnlockLogger biometricUnlockLogger,
NotificationMediaManager notificationMediaManager,
WakefulnessLifecycle wakefulnessLifecycle,
ScreenLifecycle screenLifecycle,
@@ -308,6 +307,7 @@
mSessionTracker = sessionTracker;
mScreenOffAnimationController = screenOffAnimationController;
mVibratorHelper = vibrator;
+ mLogger = biometricUnlockLogger;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -329,9 +329,7 @@
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@Override
public void run() {
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "biometric wakelock: TIMEOUT!!");
- }
+ mLogger.i("biometric wakelock: TIMEOUT!!");
releaseBiometricWakeLock();
}
};
@@ -339,9 +337,7 @@
private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "releasing biometric wakelock");
- }
+ mLogger.i("releasing biometric wakelock");
mWakeLock.release();
mWakeLock = null;
}
@@ -372,9 +368,7 @@
Trace.beginSection("acquiring wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "biometric acquired, grabbing biometric wakelock");
- }
+ mLogger.i("biometric acquired, grabbing biometric wakelock");
mHandler.postDelayed(mReleaseBiometricWakeLockRunnable,
BIOMETRIC_WAKELOCK_TIMEOUT_MS);
}
@@ -411,7 +405,7 @@
mKeyguardViewMediator.userActivity();
startWakeAndUnlock(biometricSourceType, isStrongBiometric);
} else {
- Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
+ mLogger.d("onBiometricAuthenticated aborted by bypass controller");
}
}
@@ -427,7 +421,7 @@
}
public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
- Log.v(TAG, "startWakeAndUnlock(" + mode + ")");
+ mLogger.logStartWakeAndUnlock(mode);
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
mMode = mode;
mHasScreenTurnedOnSinceAuthenticating = false;
@@ -442,9 +436,7 @@
// brightness changes due to display state transitions.
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "bio wakelock: Authenticated, waking up...");
- }
+ mLogger.i("bio wakelock: Authenticated, waking up...");
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"android.policy:BIOMETRIC");
}
@@ -537,13 +529,16 @@
}
private @WakeAndUnlockMode int calculateModeForFingerprint(boolean isStrongBiometric) {
- boolean unlockingAllowed =
+ final boolean unlockingAllowed =
mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
- boolean deviceDreaming = mUpdateMonitor.isDreaming();
+ final boolean deviceInteractive = mUpdateMonitor.isDeviceInteractive();
+ final boolean keyguardShowing = mKeyguardStateController.isShowing();
+ final boolean deviceDreaming = mUpdateMonitor.isDreaming();
- if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardStateController.isShowing()
- && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
+ logCalculateModeForFingerprint(unlockingAllowed, deviceInteractive,
+ keyguardShowing, deviceDreaming, isStrongBiometric);
+ if (!deviceInteractive) {
+ if (!keyguardShowing && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
if (mKeyguardStateController.isUnlocked()) {
return MODE_WAKE_AND_UNLOCK;
}
@@ -559,7 +554,7 @@
if (unlockingAllowed && deviceDreaming) {
return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
}
- if (mKeyguardStateController.isShowing()) {
+ if (keyguardShowing) {
if (mKeyguardViewController.primaryBouncerIsOrWillBeShowing() && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed) {
@@ -571,14 +566,39 @@
return MODE_NONE;
}
+ private void logCalculateModeForFingerprint(boolean unlockingAllowed, boolean deviceInteractive,
+ boolean keyguardShowing, boolean deviceDreaming, boolean strongBiometric) {
+ if (unlockingAllowed) {
+ mLogger.logCalculateModeForFingerprintUnlockingAllowed(deviceInteractive,
+ keyguardShowing, deviceDreaming);
+ } else {
+ // if unlocking isn't allowed, log more information about why unlocking may not
+ // have been allowed
+ final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
+ KeyguardUpdateMonitor.getCurrentUser());
+ final boolean nonStrongBiometricAllowed =
+ mUpdateMonitor.getStrongAuthTracker()
+ .isNonStrongBiometricAllowedAfterIdleTimeout(
+ KeyguardUpdateMonitor.getCurrentUser());
+
+ mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric,
+ strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing);
+ }
+ }
+
private @WakeAndUnlockMode int calculateModeForPassiveAuth(boolean isStrongBiometric) {
- boolean unlockingAllowed =
+ final boolean deviceInteractive = mUpdateMonitor.isDeviceInteractive();
+ final boolean isKeyguardShowing = mKeyguardStateController.isShowing();
+ final boolean unlockingAllowed =
mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
- boolean deviceDreaming = mUpdateMonitor.isDreaming();
- boolean bypass = mKeyguardBypassController.getBypassEnabled()
+ final boolean deviceDreaming = mUpdateMonitor.isDreaming();
+ final boolean bypass = mKeyguardBypassController.getBypassEnabled()
|| mAuthController.isUdfpsFingerDown();
- if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardStateController.isShowing()) {
+
+ logCalculateModeForPassiveAuth(unlockingAllowed, deviceInteractive, isKeyguardShowing,
+ deviceDreaming, bypass, isStrongBiometric);
+ if (!deviceInteractive) {
+ if (!isKeyguardShowing) {
return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
} else if (!unlockingAllowed) {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -602,11 +622,11 @@
if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
return MODE_UNLOCK_COLLAPSING;
}
- if (mKeyguardStateController.isShowing()) {
+ if (isKeyguardShowing) {
if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
|| mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
- } else if (unlockingAllowed && (bypass || mAuthController.isUdfpsFingerDown())) {
+ } else if (unlockingAllowed && bypass) {
return MODE_UNLOCK_COLLAPSING;
} else {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -615,6 +635,28 @@
return MODE_NONE;
}
+ private void logCalculateModeForPassiveAuth(boolean unlockingAllowed,
+ boolean deviceInteractive, boolean keyguardShowing, boolean deviceDreaming,
+ boolean bypass, boolean strongBiometric) {
+ if (unlockingAllowed) {
+ mLogger.logCalculateModeForPassiveAuthUnlockingAllowed(
+ deviceInteractive, keyguardShowing, deviceDreaming, bypass);
+ } else {
+ // if unlocking isn't allowed, log more information about why unlocking may not
+ // have been allowed
+ final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
+ KeyguardUpdateMonitor.getCurrentUser());
+ final boolean nonStrongBiometricAllowed =
+ mUpdateMonitor.getStrongAuthTracker()
+ .isNonStrongBiometricAllowedAfterIdleTimeout(
+ KeyguardUpdateMonitor.getCurrentUser());
+
+ mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed(
+ strongBiometric, strongAuthFlags, nonStrongBiometricAllowed,
+ deviceInteractive, keyguardShowing, bypass);
+ }
+ }
+
@Override
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
@@ -632,6 +674,7 @@
if (!mVibratorHelper.hasVibrator()
&& (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+ mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
startWakeAndUnlock(MODE_ONLY_WAKE);
} else if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
@@ -644,6 +687,7 @@
mLastFpFailureUptimeMillis = currUptimeMillis;
if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+ mLogger.logUdfpsAttemptThresholdMet(mNumConsecutiveFpFailures);
startWakeAndUnlock(MODE_SHOW_BOUNCER);
UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
mNumConsecutiveFpFailures = 0;
@@ -674,6 +718,7 @@
&& (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
if (fingerprintLockout) {
+ mLogger.d("fingerprint locked out");
startWakeAndUnlock(MODE_SHOW_BOUNCER);
UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index fcf33b4..dcbabaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -475,7 +475,7 @@
} else {
mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
- } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
+ } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
&& !(mBiometricUnlockController.getMode() == MODE_SHOW_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index d411e34..ae30ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -94,7 +94,7 @@
overlayContainer = builder.build()
SurfaceControl.Transaction()
- .setLayer(overlayContainer, Integer.MAX_VALUE)
+ .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
.show(overlayContainer)
.apply()
@@ -268,4 +268,12 @@
this.isFolded = isFolded
}
)
+
+ private companion object {
+ private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+
+ // Put the unfold overlay below the rotation animation screenshot to hide the moment
+ // when it is rotated but the rotation of the other windows hasn't happen yet
+ private const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 0f06de2..3655232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -5,6 +5,7 @@
import android.testing.TestableLooper
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
@@ -79,6 +80,8 @@
activityRule.launchActivity(intent)
}
+ // b/259549854 to root-cause and fix
+ @FlakyTest
@Test
fun testBackCallbackRegistrationAndUnregistration() {
// 1. ensure that launching the activity results in it registering a callback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index c94cec6..322014a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -24,8 +24,9 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
+import java.util.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -40,7 +41,6 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -93,6 +93,14 @@
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ whenever(controlsListingController.getCurrentServices())
+ .thenReturn(
+ if (hasServiceInfos) {
+ listOf(mock(), mock())
+ } else {
+ emptyList()
+ }
+ )
whenever(component.canShowWhileLockedSetting)
.thenReturn(MutableStateFlow(canShowWhileLocked))
whenever(component.getVisibility())
@@ -144,6 +152,17 @@
KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
}
)
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(
+ when {
+ !isFeatureEnabled ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice::class
+ .java
+ hasServiceInfos && hasFavorites ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java
+ else -> KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java
+ }
+ )
job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 2bd8e9a..6255980 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -24,17 +24,18 @@
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -134,6 +135,33 @@
)
}
+ @Test
+ fun `getPickerScreenState - enabled if configured on device - can open camera`() = runTest {
+ whenever(controller.isAvailableOnDevice).thenReturn(true)
+ whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `getPickerScreenState - disabled if configured on device - cannot open camera`() = runTest {
+ whenever(controller.isAvailableOnDevice).thenReturn(true)
+ whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable if not configured on device`() = runTest {
+ whenever(controller.isAvailableOnDevice).thenReturn(false)
+ whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
assertThat(latest)
.isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 5178154..d875dd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -33,9 +33,11 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.wallet.controller.QuickAccessWalletController
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,6 +46,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@@ -59,7 +62,7 @@
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
- mock(),
+ context,
walletController,
activityStarter,
)
@@ -151,6 +154,44 @@
)
}
+ @Test
+ fun `getPickerScreenState - default`() = runTest {
+ setUpState()
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable`() = runTest {
+ setUpState(
+ isWalletEnabled = false,
+ )
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ @Test
+ fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+ setUpState(
+ hasWalletIcon = false,
+ )
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - disabled when there is no card`() = runTest {
+ setUpState(
+ hasSelectedCard = false,
+ )
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
+
private fun setUpState(
isWalletEnabled: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index d8a3605..bfd5190 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -133,23 +133,24 @@
}
@Test
- fun getAffordancePickerRepresentations() {
- assertThat(underTest.getAffordancePickerRepresentations())
- .isEqualTo(
- listOf(
- KeyguardQuickAffordancePickerRepresentation(
- id = config1.key,
- name = config1.pickerName,
- iconResourceId = config1.pickerIconResourceId,
- ),
- KeyguardQuickAffordancePickerRepresentation(
- id = config2.key,
- name = config2.pickerName,
- iconResourceId = config2.pickerIconResourceId,
- ),
+ fun getAffordancePickerRepresentations() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.getAffordancePickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config1.key,
+ name = config1.pickerName,
+ iconResourceId = config1.pickerIconResourceId,
+ ),
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config2.key,
+ name = config2.pickerName,
+ iconResourceId = config2.pickerIconResourceId,
+ ),
+ )
)
- )
- }
+ }
@Test
fun getSlotPickerRepresentations() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 559f183..a6fc13b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -106,6 +107,7 @@
verify(repository).setPrimaryVisible(true)
verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
verify(repository).setPrimaryShowingSoon(false)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
}
@Test
@@ -129,6 +131,7 @@
verify(repository).setPrimaryVisible(false)
verify(repository).setPrimaryHide(true)
verify(repository).setPrimaryShow(null)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 346d1e6..65210d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -31,7 +31,6 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -133,7 +132,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
}
@Test
@@ -152,7 +151,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -162,7 +161,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -172,7 +171,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -182,7 +181,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/abc.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -192,7 +191,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
}
@Test
@@ -202,21 +201,21 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
"def/.ijk", false);
verifyActivityDetails("def/.ijk");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
null, false);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
// Once from setup + twice from this function
verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
@@ -229,7 +228,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -237,14 +236,14 @@
verifyActivityDetails("def/.ijk");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
null, false);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
}
@@ -296,19 +295,19 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
// Once from setup + twice from this function
verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
}
@@ -320,13 +319,13 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
QR_CODE_SCANNER_PREFERENCE_CHANGE);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
// Unregister once again and make sure it affects the next register event
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -335,7 +334,7 @@
QR_CODE_SCANNER_PREFERENCE_CHANGE);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -345,7 +344,7 @@
/* enableOnLockScreen */ false);
assertThat(mController.getIntent()).isNotNull();
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
assertThat(getSettingsQRCodeDefaultComponent()).isNull();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index cac90a1..a1be2f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
import static org.mockito.Mockito.when;
@@ -108,17 +109,20 @@
@Test
public void testQRCodeTileUnavailable() {
- when(mController.isEnabledForQuickSettings()).thenReturn(false);
+ when(mController.isAbleToOpenCameraApp()).thenReturn(false);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
assertEquals(state.state, Tile.STATE_UNAVAILABLE);
+ assertEquals(state.secondaryLabel.toString(),
+ mContext.getString(R.string.qr_code_scanner_updating_secondary_label));
}
@Test
public void testQRCodeTileAvailable() {
- when(mController.isEnabledForQuickSettings()).thenReturn(true);
+ when(mController.isAbleToOpenCameraApp()).thenReturn(true);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
assertEquals(state.state, Tile.STATE_INACTIVE);
+ assertNull(state.secondaryLabel);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 2a3d32e..9c36be6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -344,26 +344,6 @@
}
@Test
- fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
- val views = mapOf(
- R.id.clock to "clock",
- R.id.date to "date",
- R.id.statusIcons to "icons",
- R.id.privacy_container to "privacy",
- R.id.carrier_group to "carriers",
- R.id.batteryRemainingIcon to "battery",
- )
- views.forEach { (id, name) ->
- assertWithMessage("$name changes height")
- .that(qqsConstraint.getConstraint(id).layout.mHeight)
- .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight)
- assertWithMessage("$name changes width")
- .that(qqsConstraint.getConstraint(id).layout.mWidth)
- .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth)
- }
- }
-
- @Test
fun testEmptyCutoutDateIconsAreConstrainedWidth() {
CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 75a3b21..d1957ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -42,6 +42,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dump.DumpManager;
@@ -78,6 +79,8 @@
@Mock
private KeyguardUpdateMonitor mUpdateMonitor;
@Mock
+ private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
+ @Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock
private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -119,6 +122,8 @@
private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
private VibratorHelper mVibratorHelper;
+ @Mock
+ private BiometricUnlockLogger mLogger;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -138,12 +143,13 @@
mKeyguardViewMediator, mScrimController, mShadeController,
mNotificationShadeWindowController, mKeyguardStateController, mHandler,
mUpdateMonitor, res.getResources(), mKeyguardBypassController,
- mMetricsLogger, mDumpManager, mPowerManager,
+ mMetricsLogger, mDumpManager, mPowerManager, mLogger,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
}
@Test
@@ -285,6 +291,7 @@
@Test
public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showPrimaryBouncer() {
reset(mUpdateMonitor);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
@@ -322,6 +329,7 @@
@Test
public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
reset(mUpdateMonitor);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index bee882d..d42f757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,6 +77,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -157,6 +158,7 @@
import java.util.List;
import java.util.Optional;
+@FlakyTest
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 78df983..166806b 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -357,13 +357,24 @@
* Starts the given user.
*/
public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
-
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
+ // On Automotive / Headless System User Mode, the system user will be started twice:
+ // - Once by some external or local service that switches the system user to
+ // the background.
+ // - Once by the ActivityManagerService, when the system is marked ready.
+ // These two events are not synchronized and the order of execution is
+ // non-deterministic. To avoid starting the system user twice, verify whether
+ // the system user has already been started by checking the mTargetUsers.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+ Slog.e(TAG, "Skipping starting system user twice");
+ return;
+ }
mTargetUsers.put(userId, targetUser);
}
-
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fde96b9..bc083f1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8163,15 +8163,12 @@
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
Integer.toString(currentUserId), currentUserId);
- // On Automotive, at this point the system user has already been started and unlocked,
- // and some of the tasks we do here have already been done. So skip those in that case.
- // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the
- // headless-user start logic to UserManager-land
- final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-
- if (bootingSystemUser) {
- mSystemServiceManager.onUserStarting(t, currentUserId);
- }
+ // On Automotive / Headless System User Mode, at this point the system user has already been
+ // started and unlocked, and some of the tasks we do here have already been done. So skip
+ // those in that case. The duplicate system user start is guarded in SystemServiceManager.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
+ // start logic to UserManager-land.
+ mSystemServiceManager.onUserStarting(t, currentUserId);
synchronized (this) {
// Only start up encryption-aware persistent apps; once user is
@@ -8201,7 +8198,15 @@
t.traceEnd();
}
- if (bootingSystemUser) {
+ // Some systems - like automotive - will explicitly unlock system user then switch
+ // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+ // the system user here.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+ && !UserManager.isHeadlessSystemUserMode();
+
+ if (isBootingSystemUser) {
t.traceBegin("startHomeOnAllDisplays");
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
t.traceEnd();
@@ -8212,7 +8217,7 @@
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -8253,7 +8258,7 @@
mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserSwitchBroadcasts");
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
t.traceEnd();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fc15890..66992aa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5261,42 +5261,7 @@
}
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
- // Note that we ignore display frozen since we want the opening / closing transition type
- // can be updated correctly even display frozen, and it's safe since in applyAnimation will
- // still check DC#okToAnimate again if the transition animation is fine to apply.
- // TODO(new-app-transition): Rewrite this logic using WM Shell.
- final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
- final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode
- && mVisible == visible;
- if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
- && (appTransition.isTransitionSet()
- || (recentsAnimating && !isActivityTypeHome()))
- // If the visibility is not changed during enter PIP, we don't want to include it in
- // app transition to affect the animation theme, because the Pip organizer will
- // animate the entering PIP instead.
- && !isEnteringPipWithoutVisibleChange) {
- if (visible) {
- displayContent.mOpeningApps.add(this);
- mEnteringAnimation = true;
- } else if (mVisible) {
- displayContent.mClosingApps.add(this);
- mEnteringAnimation = false;
- }
- if ((appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
- // We're launchingBehind, add the launching activity to mOpeningApps.
- final WindowState win = getDisplayContent().findFocusedWindow();
- if (win != null) {
- final ActivityRecord focusedActivity = win.mActivityRecord;
- if (focusedActivity != null) {
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
- focusedActivity);
-
- // Force animation to be loaded.
- displayContent.mOpeningApps.add(focusedActivity);
- }
- }
- }
+ if (deferCommitVisibilityChange(visible)) {
return;
}
@@ -5304,6 +5269,61 @@
updateReportedVisibilityLocked();
}
+ /**
+ * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
+ * Then its visibility will be committed until the transition is ready.
+ */
+ private boolean deferCommitVisibilityChange(boolean visible) {
+ if (!mDisplayContent.mAppTransition.isTransitionSet()) {
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // Shell transition doesn't use opening/closing sets.
+ return false;
+ }
+ // Defer committing visibility for non-home app which is animating by recents.
+ if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ return false;
+ }
+ }
+ if (mWaitForEnteringPinnedMode && mVisible == visible) {
+ // If the visibility is not changed during enter PIP, we don't want to include it in
+ // app transition to affect the animation theme, because the Pip organizer will
+ // animate the entering PIP instead.
+ return false;
+ }
+
+ // The animation will be visible soon so do not skip by screen off.
+ final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
+ .isKeyguardGoingAway(mDisplayContent.mDisplayId);
+ // Ignore display frozen so the opening / closing transition type can be updated correctly
+ // even if the display is frozen. And it's safe since in applyAnimation will still check
+ // DC#okToAnimate again if the transition animation is fine to apply.
+ if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
+ return false;
+ }
+ if (visible) {
+ mDisplayContent.mOpeningApps.add(this);
+ mEnteringAnimation = true;
+ } else if (mVisible) {
+ mDisplayContent.mClosingApps.add(this);
+ mEnteringAnimation = false;
+ }
+ if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
+ // Add the launching-behind activity to mOpeningApps.
+ final WindowState win = mDisplayContent.findFocusedWindow();
+ if (win != null) {
+ final ActivityRecord focusedActivity = win.mActivityRecord;
+ if (focusedActivity != null) {
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+ "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
+ focusedActivity);
+ // Force animation to be loaded.
+ mDisplayContent.mOpeningApps.add(focusedActivity);
+ }
+ }
+ }
+ return true;
+ }
+
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index eec7801..a3554cd 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -3037,12 +3037,6 @@
newParent = candidateTf;
}
}
- if (newParent.asTask() == null) {
- // only collect task-fragments.
- // TODO(b/258095975): we probably shouldn't ever collect the parent here since it isn't
- // changing. The logic that changes it should collect it.
- newParent.mTransitionController.collect(newParent);
- }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 38f6a53..3a936a5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4935,9 +4935,8 @@
@Override
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
return okToDisplay(ignoreFrozen, ignoreScreenOn)
- && (mDisplayId != DEFAULT_DISPLAY
- || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
- && getDisplayPolicy().isScreenOnFully();
+ && (mDisplayId != DEFAULT_DISPLAY || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+ && (ignoreFrozen || mDisplayPolicy.isScreenOnFully());
}
static final class TaskForResizePointSearchResult implements Predicate<Task> {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index 91469a0..09a17e1 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -46,6 +46,8 @@
private static final int DISPLAY_EDGE_OFFSET_DP = 27;
+ private static final Rect TMP_STABLE_BOUNDS = new Rect();
+
private LaunchParamsUtil() {}
/**
@@ -130,18 +132,42 @@
return new Size(adjWidth, adjHeight);
}
- static void adjustBoundsToFitInDisplayArea(@NonNull Rect stableBounds, int layoutDirection,
+ static void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
+ int layoutDirection,
@NonNull ActivityInfo.WindowLayout layout,
@NonNull Rect inOutBounds) {
+ // Give a small margin between the window bounds and the display bounds.
+ final Rect stableBounds = TMP_STABLE_BOUNDS;
+ displayArea.getStableRect(stableBounds);
+ final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int displayEdgeOffset = (int) (DISPLAY_EDGE_OFFSET_DP * density + 0.5f);
+ stableBounds.inset(displayEdgeOffset, displayEdgeOffset);
+
if (stableBounds.width() < inOutBounds.width()
|| stableBounds.height() < inOutBounds.height()) {
- // There is no way for us to fit the bounds in the displayArea without changing width
- // or height. Just move the start to align with the displayArea.
- final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
- ? stableBounds.right - inOutBounds.right + inOutBounds.left
- : stableBounds.left;
- inOutBounds.offsetTo(left, stableBounds.top);
- return;
+ final float heightShrinkRatio = stableBounds.width() / (float) inOutBounds.width();
+ final float widthShrinkRatio =
+ stableBounds.height() / (float) inOutBounds.height();
+ final float shrinkRatio = Math.min(heightShrinkRatio, widthShrinkRatio);
+ // Minimum layout requirements.
+ final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
+ final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
+ int adjustedWidth = Math.max(layoutMinWidth, (int) (inOutBounds.width() * shrinkRatio));
+ int adjustedHeight = Math.max(layoutMinHeight,
+ (int) (inOutBounds.height() * shrinkRatio));
+ if (stableBounds.width() < adjustedWidth
+ || stableBounds.height() < adjustedHeight) {
+ // There is no way for us to fit the bounds in the displayArea without breaking min
+ // size constraints. Set the min size to make visible as much content as possible.
+ final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
+ ? stableBounds.right - adjustedWidth
+ : stableBounds.left;
+ inOutBounds.set(left, stableBounds.top, left + adjustedWidth,
+ stableBounds.top + adjustedHeight);
+ return;
+ }
+ inOutBounds.set(inOutBounds.left, inOutBounds.top,
+ inOutBounds.left + adjustedWidth, inOutBounds.top + adjustedHeight);
}
final int dx;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 43b315c..1088a2e 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -917,6 +917,7 @@
// Update windowing mode if necessary, e.g. launch into a different windowing mode.
if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
&& candidateTask.getWindowingMode() != windowingMode) {
+ candidateTask.mTransitionController.collect(candidateTask);
candidateTask.setWindowingMode(windowingMode);
}
return candidateTask.getRootTask();
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index f51a173..3949952 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -277,29 +277,31 @@
// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
// different, we should recalcuating the bounds.
boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = false;
- if (suggestedDisplayArea.inFreeformWindowingMode()) {
- if (launchMode == WINDOWING_MODE_PINNED) {
- if (DEBUG) appendLog("picture-in-picture");
- } else if (!root.isResizeable()) {
- if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) {
- launchMode = WINDOWING_MODE_FREEFORM;
- if (outParams.mBounds.isEmpty()) {
- getTaskBounds(root, suggestedDisplayArea, layout, launchMode,
- hasInitialBounds, outParams.mBounds);
- hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true;
- }
- if (DEBUG) appendLog("unresizable-freeform");
- } else {
- launchMode = WINDOWING_MODE_FULLSCREEN;
- outParams.mBounds.setEmpty();
- if (DEBUG) appendLog("unresizable-forced-maximize");
+ // shouldSetAsOverrideWindowingMode is set if the task needs to retain the launchMode
+ // regardless of the windowing mode of the parent.
+ boolean shouldSetAsOverrideWindowingMode = false;
+ if (launchMode == WINDOWING_MODE_PINNED) {
+ if (DEBUG) appendLog("picture-in-picture");
+ } else if (!root.isResizeable()) {
+ if (shouldLaunchUnresizableAppInFreeformInFreeformMode(root, suggestedDisplayArea,
+ options)) {
+ launchMode = WINDOWING_MODE_UNDEFINED;
+ if (outParams.mBounds.isEmpty()) {
+ getTaskBounds(root, suggestedDisplayArea, layout, launchMode, hasInitialBounds,
+ outParams.mBounds);
+ hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true;
}
+ if (DEBUG) appendLog("unresizable-freeform");
+ } else {
+ launchMode = WINDOWING_MODE_FULLSCREEN;
+ outParams.mBounds.setEmpty();
+ shouldSetAsOverrideWindowingMode = true;
+ if (DEBUG) appendLog("unresizable-forced-maximize");
}
- } else {
- if (DEBUG) appendLog("non-freeform-task-display-area");
}
// If launch mode matches display windowing mode, let it inherit from display.
outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode()
+ && !shouldSetAsOverrideWindowingMode
? WINDOWING_MODE_UNDEFINED : launchMode;
if (phase == PHASE_WINDOWING_MODE) {
@@ -650,7 +652,7 @@
inOutBounds.offset(xOffset, yOffset);
}
- private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
+ private boolean shouldLaunchUnresizableAppInFreeformInFreeformMode(ActivityRecord activity,
TaskDisplayArea displayArea, @Nullable ActivityOptions options) {
if (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
// Do not launch the activity in freeform if it explicitly requested fullscreen mode.
@@ -663,8 +665,7 @@
final int displayOrientation = orientationFromBounds(displayArea.getBounds());
final int activityOrientation = resolveOrientation(activity, displayArea,
displayArea.getBounds());
- if (displayArea.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && displayOrientation != activityOrientation) {
+ if (displayOrientation != activityOrientation) {
return true;
}
@@ -768,9 +769,10 @@
// to the center of suggested bounds (or the displayArea if no suggested bounds). The
// default size might be too big to center to source activity bounds in displayArea, so
// we may need to move it back to the displayArea.
+ adjustBoundsToFitInDisplayArea(displayArea, layout, mTmpBounds);
+ inOutBounds.setEmpty();
LaunchParamsUtil.centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(),
inOutBounds);
- adjustBoundsToFitInDisplayArea(displayArea, layout, inOutBounds);
if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds);
}
@@ -821,8 +823,7 @@
@NonNull Rect inOutBounds) {
final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration()
.getLayoutDirection();
- displayArea.getStableRect(mTmpStableBounds);
- LaunchParamsUtil.adjustBoundsToFitInDisplayArea(mTmpStableBounds, layoutDirection, layout,
+ LaunchParamsUtil.adjustBoundsToFitInDisplayArea(displayArea, layoutDirection, layout,
inOutBounds);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ff5622f..d3e638f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3091,6 +3091,17 @@
assertTrue(activity.mVisibleRequested);
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+
+ // There should still be animation (add to opening) if keyguard is going away while the
+ // screen is off because it will be visible after screen is turned on by unlocking.
+ mDisplayContent.mOpeningApps.remove(activity);
+ mDisplayContent.mClosingApps.remove(activity);
+ activity.commitVisibility(false /* visible */, false /* performLayout */);
+ mDisplayContent.getDisplayPolicy().screenTurnedOff();
+ final KeyguardController controller = mSupervisor.getKeyguardController();
+ doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
+ activity.setVisibility(true);
+ assertTrue(mDisplayContent.mOpeningApps.contains(activity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index dce9f66..2a88eb0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -85,6 +85,11 @@
private static final Rect DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
/* top */ 200, /* right */ 1620, /* bottom */ 680);
+ private static final Rect SMALL_DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
+ /* right */ 1000, /* bottom */ 500);
+ private static final Rect SMALL_DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
+ /* top */ 50, /* right */ 900, /* bottom */ 450);
+
private ActivityRecord mActivity;
private TaskLaunchParamsModifier mTarget;
@@ -820,10 +825,11 @@
}
@Test
- public void testLaunchesPortraitUnresizableOnFreeformDisplayWithFreeformSizeCompat() {
+ public void testLaunchesPortraitUnresizableOnFreeformLandscapeDisplay() {
mAtm.mDevEnableNonResizableMultiWindow = true;
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
+ assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height());
final ActivityOptions options = ActivityOptions.makeBasic();
mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
@@ -831,12 +837,42 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
- WINDOWING_MODE_FREEFORM);
+ assertEquals(WINDOWING_MODE_UNDEFINED, mResult.mWindowingMode);
}
@Test
- public void testSkipsForceMaximizingAppsOnNonFreeformDisplay() {
+ public void testLaunchesLandscapeUnresizableOnFreeformLandscapeDisplay() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+ assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height());
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode);
+ }
+
+ @Test
+ public void testLaunchesUndefinedUnresizableOnFreeformLandscapeDisplay() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+ assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height());
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode);
+ }
+
+ @Test
+ public void testForceMaximizingAppsOnNonFreeformDisplay() {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
options.setLaunchBounds(new Rect(0, 0, 200, 100));
@@ -850,8 +886,9 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
- WINDOWING_MODE_FULLSCREEN);
+ // Non-resizable apps must be launched in fullscreen in a fullscreen display regardless of
+ // other properties.
+ assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode);
}
@Test
@@ -1346,6 +1383,20 @@
}
@Test
+ public void testDefaultFreeformSizeShrinksOnSmallDisplay() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM, SMALL_DISPLAY_BOUNDS, SMALL_DISPLAY_STABLE_BOUNDS);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setOptions(options)
+ .calculate());
+
+ assertEquals(new Rect(414, 77, 587, 423), mResult.mBounds);
+ }
+
+ @Test
public void testDefaultFreeformSizeRespectsMinAspectRatio() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1535,16 +1586,15 @@
options.setLaunchDisplayId(freeformDisplay.mDisplayId);
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- mCurrent.mBounds.set(100, 300, 1820, 1380);
+ mCurrent.mBounds.set(0, 0, 3000, 2000);
mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertTrue("Result bounds should start from app bounds's origin, but it's "
- + mResult.mBounds,
- mResult.mBounds.left == 100 && mResult.mBounds.top == 200);
+ // Must shrink to fit the display while reserving aspect ratio.
+ assertEquals(new Rect(127, 227, 766, 653), mResult.mBounds);
}
@Test
@@ -1560,18 +1610,19 @@
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setMinWidth(500).setMinHeight(500).build();
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- mCurrent.mBounds.set(100, 300, 1820, 1380);
+ mCurrent.mBounds.set(0, 0, 2000, 3000);
mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
assertEquals(RESULT_CONTINUE,
- new CalculateRequestBuilder().setOptions(options).calculate());
+ new CalculateRequestBuilder().setOptions(options).setLayout(layout).calculate());
- assertTrue("Result bounds should start from top-right corner of app bounds, but "
- + "it's " + mResult.mBounds,
- mResult.mBounds.left == -100 && mResult.mBounds.top == 200);
+ // Must shrink to fit the display while reserving aspect ratio.
+ assertEquals(new Rect(1093, 227, 1593, 727), mResult.mBounds);
}
@Test
@@ -1746,7 +1797,7 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertEquals(new Rect(100, 200, 400, 500), mResult.mBounds);
+ assertEquals(new Rect(127, 227, 427, 527), mResult.mBounds);
}
@Test
@@ -1799,13 +1850,18 @@
}
private TestDisplayContent createNewDisplayContent(int windowingMode) {
+ return createNewDisplayContent(windowingMode, DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS);
+ }
+
+ private TestDisplayContent createNewDisplayContent(int windowingMode, Rect displayBounds,
+ Rect displayStableBounds) {
final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode);
- display.setBounds(DISPLAY_BOUNDS);
+ display.setBounds(displayBounds);
display.getConfiguration().densityDpi = DENSITY_DEFAULT;
display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
configInsetsState(display.getInsetsStateController().getRawInsetsState(), display,
- DISPLAY_STABLE_BOUNDS);
+ displayStableBounds);
return display;
}