diff options
38 files changed, 671 insertions, 399 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5c87042fe33d..b472ecbe6ee7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -342,22 +342,6 @@ <!-- Mask to use when checking skb mark defined in config_networkWakeupPacketMark above. --> <integer name="config_networkWakeupPacketMask">0</integer> - <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames - Those frames are identified by the field Eth-type having values - less than 0x600 --> - <bool translatable="false" name="config_apfDrop802_3Frames">true</bool> - - <!-- An array of Denylisted EtherType, packets with EtherTypes within this array - will be dropped - TODO: need to put proper values, these are for testing purposes only --> - <integer-array translatable="false" name="config_apfEthTypeBlackList"> - <item>0x88A2</item> - <item>0x88A4</item> - <item>0x88B8</item> - <item>0x88CD</item> - <item>0x88E3</item> - </integer-array> - <!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual device behaviour is controlled by Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE. This is the default value of that setting. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1f799428b243..e47764e110f7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2032,8 +2032,6 @@ <java-symbol type="integer" name="config_networkAvoidBadWifi" /> <java-symbol type="integer" name="config_networkWakeupPacketMark" /> <java-symbol type="integer" name="config_networkWakeupPacketMask" /> - <java-symbol type="bool" name="config_apfDrop802_3Frames" /> - <java-symbol type="array" name="config_apfEthTypeBlackList" /> <java-symbol type="integer" name="config_networkDefaultDailyMultipathQuotaBytes" /> <java-symbol type="integer" name="config_networkMeteredMultipathPreference" /> <java-symbol type="array" name="config_networkSupportedKeepaliveCount" /> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index f81c9f80830a..16f1d1c2944c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -23,7 +23,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.RemoteAction; @@ -71,11 +70,6 @@ public interface PipMenuController { void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction); /** - * Wait until the next frame to run the given Runnable. - */ - void runWithNextFrame(@NonNull Runnable runnable); - - /** * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a * need to synchronize the movements on the same frame as PiP. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 2d7c5ce6feb5..f170e774739f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -179,10 +179,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // This is necessary in case there was a resize animation ongoing when exit PIP // started, in which case the first resize will be skipped to let the exit // operation handle the final resize out of PIP mode. See b/185306679. - finishResizeDelayedIfNeeded(() -> { - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); - }); + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); } } @@ -198,34 +196,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; - /** - * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. - * - * This is done to avoid a race condition between the last transaction applied in - * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a - * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it - * gets applied before the transaction created by the last onAnimationUpdate. As a result of - * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which - * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize - * operation until the next frame. This aligns the last onAnimationUpdate transaction with the - * WCT application. - * - * The race only happens when the PiP surface transaction has to be synced with the PiP menu - * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and - * the PiP menu contents. - */ - private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) { - if (!shouldSyncPipTransactionWithMenu()) { - finishResizeRunnable.run(); - return; - } - mPipMenuController.runWithNextFrame(finishResizeRunnable); - } - - private boolean shouldSyncPipTransactionWithMenu() { - return mPipMenuController.isMenuVisible(); - } - @VisibleForTesting final PipTransitionController.PipTransitionCallback mPipTransitionCallback = new PipTransitionController.PipTransitionCallback() { @@ -251,7 +221,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds) { - if (shouldSyncPipTransactionWithMenu()) { + if (mPipMenuController.isMenuVisible()) { mPipMenuController.movePipMenu(leash, tx, destinationBounds); return true; } @@ -1253,7 +1223,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .crop(tx, mLeash, toBounds) .round(tx, mLeash, mPipTransitionState.isInPip()); - if (shouldSyncPipTransactionWithMenu()) { + if (mPipMenuController.isMenuVisible()) { mPipMenuController.resizePipMenu(mLeash, tx, toBounds); } else { tx.apply(); @@ -1295,7 +1265,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); - if (shouldSyncPipTransactionWithMenu()) { + if (mPipMenuController.isMenuVisible()) { mPipMenuController.movePipMenu(mLeash, tx, toBounds); } else { tx.apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 27902b2231ba..281ea530e9e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -305,18 +305,6 @@ public class PhonePipMenuController implements PipMenuController { showResizeHandle); } - @Override - public void runWithNextFrame(Runnable runnable) { - if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { - runnable.run(); - } - - mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> { - mMainHandler.post(runnable); - }); - mPipMenuView.invalidate(); - } - /** * Move the PiP menu, which does a translation and possibly a scale transformation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 7d4b43be4f73..4ce45e142c64 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -466,18 +466,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @Override - public void runWithNextFrame(Runnable runnable) { - if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { - runnable.run(); - } - - mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> { - mMainHandler.post(runnable); - }); - mPipMenuView.invalidate(); - } - - @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction, Rect pipDestBounds) { if (DEBUG) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index f6d6c03bc2ee..5332476d5130 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -140,7 +140,7 @@ public class SplitLayoutTests extends ShellTestCase { DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START); - mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); + mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget); waitDividerFlingFinished(); verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt()); } @@ -152,7 +152,7 @@ public class SplitLayoutTests extends ShellTestCase { DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END); - mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); + mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget); waitDividerFlingFinished(); verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt()); } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a544d9b7af39..b3b5529897da 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -115,6 +115,7 @@ android_library { "androidx-constraintlayout_constraintlayout", "androidx.exifinterface_exifinterface", "com.google.android.material_material", + "kotlin-reflect", "kotlinx_coroutines_android", "kotlinx_coroutines", "iconloader_base", diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml index 88d8f78fa610..569ee76586c2 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml @@ -30,7 +30,7 @@ </item> <item android:id="@android:id/progress" android:gravity="center_vertical|fill_horizontal"> - <com.android.systemui.util.RoundedCornerProgressDrawable + <com.android.systemui.util.BrightnessProgressDrawable android:drawable="@drawable/brightness_progress_full_drawable" /> </item> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 93926ef9e780..771d0d19708a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -407,7 +407,7 @@ <dimen name="match_parent">-1px</dimen> <!-- Height of status bar in split shade mode - visible only on large screens --> - <dimen name="large_screen_shade_header_height">@*android:dimen/quick_qs_offset_height</dimen> + <dimen name="large_screen_shade_header_height">48dp</dimen> <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen> <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a7f8cb6f695e..c88da1895e8d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -403,6 +403,8 @@ <string name="keyguard_face_failed">Can\u2019t recognize face</string> <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] --> <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string> + <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] --> + <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string> <!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_bluetooth_connected">Bluetooth connected.</string> diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml index cdbf8ab0be41..06d425c57577 100644 --- a/packages/SystemUI/res/xml/large_screen_shade_header.xml +++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml @@ -107,7 +107,7 @@ android:id="@+id/privacy_container"> <Layout android:layout_width="wrap_content" - android:layout_height="0dp" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/date" app:layout_constraintBottom_toBottomOf="@id/date" diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index 88b4f43b440b..af4be1ade656 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -98,7 +98,7 @@ android:id="@+id/privacy_container"> <Layout android:layout_width="wrap_content" - android:layout_height="0dp" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toEndOf="@id/end_guide" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index f7049cf8f4f2..933e58639ccf 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -89,7 +89,7 @@ abstract class BooleanFlag constructor( * * It can be changed or overridden in debug builds but not in release builds. */ -data class UnreleasedFlag @JvmOverloads constructor( +data class UnreleasedFlag constructor( override val id: Int, override val teamfood: Boolean = false, override val overridden: Boolean = false @@ -100,7 +100,7 @@ data class UnreleasedFlag @JvmOverloads constructor( * * It can be changed or overridden in any build, meaning it can be turned off if needed. */ -data class ReleasedFlag @JvmOverloads constructor( +data class ReleasedFlag constructor( override val id: Int, override val teamfood: Boolean = false, override val overridden: Boolean = false @@ -111,7 +111,7 @@ data class ReleasedFlag @JvmOverloads constructor( * * Prefer [UnreleasedFlag] and [ReleasedFlag]. */ -data class ResourceBooleanFlag @JvmOverloads constructor( +data class ResourceBooleanFlag constructor( override val id: Int, @BoolRes override val resourceId: Int, override val teamfood: Boolean = false @@ -124,7 +124,7 @@ data class ResourceBooleanFlag @JvmOverloads constructor( * * Prefer [UnreleasedFlag] and [ReleasedFlag]. */ -data class DeviceConfigBooleanFlag @JvmOverloads constructor( +data class DeviceConfigBooleanFlag constructor( override val id: Int, override val name: String, override val namespace: String, @@ -139,7 +139,7 @@ data class DeviceConfigBooleanFlag @JvmOverloads constructor( * * Prefer [UnreleasedFlag] and [ReleasedFlag]. */ -data class SysPropBooleanFlag @JvmOverloads constructor( +data class SysPropBooleanFlag constructor( override val id: Int, override val name: String, override val default: Boolean = false @@ -148,7 +148,7 @@ data class SysPropBooleanFlag @JvmOverloads constructor( override val teamfood: Boolean = false } -data class StringFlag @JvmOverloads constructor( +data class StringFlag constructor( override val id: Int, override val default: String = "", override val teamfood: Boolean = false, @@ -173,13 +173,13 @@ data class StringFlag @JvmOverloads constructor( } } -data class ResourceStringFlag @JvmOverloads constructor( +data class ResourceStringFlag constructor( override val id: Int, @StringRes override val resourceId: Int, override val teamfood: Boolean = false ) : ResourceFlag<String> -data class IntFlag @JvmOverloads constructor( +data class IntFlag constructor( override val id: Int, override val default: Int = 0, override val teamfood: Boolean = false, @@ -205,13 +205,13 @@ data class IntFlag @JvmOverloads constructor( } } -data class ResourceIntFlag @JvmOverloads constructor( +data class ResourceIntFlag constructor( override val id: Int, @IntegerRes override val resourceId: Int, override val teamfood: Boolean = false ) : ResourceFlag<Int> -data class LongFlag @JvmOverloads constructor( +data class LongFlag constructor( override val id: Int, override val default: Long = 0, override val teamfood: Boolean = false, @@ -237,7 +237,7 @@ data class LongFlag @JvmOverloads constructor( } } -data class FloatFlag @JvmOverloads constructor( +data class FloatFlag constructor( override val id: Int, override val default: Float = 0f, override val teamfood: Boolean = false, @@ -263,13 +263,13 @@ data class FloatFlag @JvmOverloads constructor( } } -data class ResourceFloatFlag @JvmOverloads constructor( +data class ResourceFloatFlag constructor( override val id: Int, override val resourceId: Int, override val teamfood: Boolean = false ) : ResourceFlag<Int> -data class DoubleFlag @JvmOverloads constructor( +data class DoubleFlag constructor( override val id: Int, override val default: Double = 0.0, override val teamfood: Boolean = false, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 558869c46373..1bb83f44ce6c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -798,7 +798,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintCancelSignal = null; updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_FP_AUTHENTICATED); - mLogger.d("onFingerprintAuthenticated"); + mLogger.logFingerprintSuccess(userId, isStrongBiometric); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -2716,7 +2716,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); // TODO: always disallow when fp is already locked out? - final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent; + final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent; final boolean canBypass = mKeyguardBypassController != null && mKeyguardBypassController.canBypass(); @@ -2745,7 +2745,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean faceDisabledForUser = isFaceDisabled(user); final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); - final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout; // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -2763,7 +2762,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && (!mSecureCameraLaunched || mOccludingAppRequestingFace) && !faceAuthenticated && !mGoingToSleep - && !fpOrFaceIsLockedOut; + // We only care about fp locked out state and not face because we still trigger + // face auth even when face is locked out to show the user a message that face + // unlock was supposed to run but didn't + && !fpLockedOut; // Aggregate relevant fields for debug logging. maybeLogListenerModelData( @@ -2778,7 +2780,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab faceAuthenticated, faceDisabledForUser, isFaceLockedOut(), - fpLockedout, + fpLockedOut, mGoingToSleep, awakeKeyguard, mKeyguardGoingAway, diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 3308f5550bfc..6276142d605f 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -153,6 +153,13 @@ class KeyguardUpdateMonitorLogger @Inject constructor( { "fingerprintRunningState: $int1" }) } + fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) { + logBuffer.log(TAG, DEBUG, { + int1 = userId + bool1 = isStrongBiometric + }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"}) + } + fun logInvalidSubId(subId: Int) { logBuffer.log(TAG, INFO, { int1 = subId }, diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java index 2a1e3cd937bd..ad4b87da0744 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java @@ -23,7 +23,6 @@ import androidx.annotation.NonNull; import com.android.systemui.statusbar.commandline.Command; import java.io.PrintWriter; -import java.lang.reflect.Field; import java.util.List; import java.util.Map; @@ -230,33 +229,22 @@ public class FlagCommand implements Command { } private int flagNameToId(String flagName) { - List<Field> fields = Flags.getFlagFields(); - for (Field field : fields) { - if (flagName.equals(field.getName())) { - return fieldToId(field); + Map<String, Flag<?>> flagFields = Flags.getFlagFields(); + for (String fieldName : flagFields.keySet()) { + if (flagName.equals(fieldName)) { + return flagFields.get(fieldName).getId(); } } return 0; } - private int fieldToId(Field field) { - try { - Flag<?> flag = (Flag<?>) field.get(null); - return flag.getId(); - } catch (IllegalAccessException e) { - // no-op - } - - return 0; - } - private void printKnownFlags(PrintWriter pw) { - List<Field> fields = Flags.getFlagFields(); + Map<String, Flag<?>> fields = Flags.getFlagFields(); int longestFieldName = 0; - for (Field field : fields) { - longestFieldName = Math.max(longestFieldName, field.getName().length()); + for (String fieldName : fields.keySet()) { + longestFieldName = Math.max(longestFieldName, fieldName.length()); } pw.println("Known Flags:"); @@ -268,16 +256,15 @@ public class FlagCommand implements Command { for (int i = 0; i < longestFieldName; i++) { pw.print("="); } - pw.println(" ==== ====="); - for (Field field : fields) { - int id = fieldToId(field); - Flag<?> flag = mAllFlags.get(id); - + pw.println(" ==== ========"); + for (String fieldName : fields.keySet()) { + Flag<?> flag = fields.get(fieldName); + int id = flag.getId(); if (id == 0 || !mAllFlags.containsKey(id)) { continue; } - pw.print(field.getName()); - int fieldWidth = field.getName().length(); + pw.print(fieldName); + int fieldWidth = fieldName.length(); for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) { pw.print(" "); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 0258b23d11a8..3a31d9f1031e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -17,8 +17,12 @@ package com.android.systemui.flags import android.provider.DeviceConfig import com.android.internal.annotations.Keep +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.R -import java.lang.reflect.Field +import kotlin.reflect.KClass +import kotlin.reflect.full.declaredMembers +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.full.staticProperties /** * List of [Flag] objects for use in SystemUI. @@ -63,6 +67,7 @@ object Flags { @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true) val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true) val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true) + @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, teamfood = true) // next id: 117 @@ -146,14 +151,15 @@ object Flags { // 500 - quick settings // TODO(b/254512321): Tracking Bug - @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true) + @JvmField val COMBINED_QS_HEADERS = ReleasedFlag(501) val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations) + @JvmField val QS_USER_DETAIL_SHORTCUT = ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut) // TODO(b/254512747): Tracking Bug - val NEW_HEADER = UnreleasedFlag(505, teamfood = true) + val NEW_HEADER = ReleasedFlag(505) // TODO(b/254512383): Tracking Bug @JvmField @@ -244,14 +250,14 @@ object Flags { @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002) // 1100 - windowing - @JvmField @Keep + @JvmField val WM_ENABLE_SHELL_TRANSITIONS = SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false) // TODO(b/254513207): Tracking Bug - @JvmField @Keep + @JvmField val WM_ENABLE_PARTIAL_SCREEN_SHARING = DeviceConfigBooleanFlag( 1102, @@ -262,30 +268,30 @@ object Flags { ) // TODO(b/254512674): Tracking Bug - @JvmField @Keep + @JvmField val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false) - @JvmField @Keep + @JvmField val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false) - @JvmField @Keep + @JvmField val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false) - @JvmField @Keep + @JvmField val ENABLE_FLING_TO_DISMISS_BUBBLE = SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true) - @JvmField @Keep + @JvmField val ENABLE_FLING_TO_DISMISS_PIP = SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true) - @JvmField @Keep + @JvmField val ENABLE_PIP_KEEP_CLEAR_ALGORITHM = SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false) @@ -293,18 +299,18 @@ object Flags { @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111) // 1200 - predictive back - @JvmField @Keep + @JvmField val WM_ENABLE_PREDICTIVE_BACK = SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true) - @JvmField @Keep + @JvmField val WM_ENABLE_PREDICTIVE_BACK_ANIM = SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false) - @JvmField @Keep + @JvmField val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK = SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false) @@ -313,7 +319,7 @@ object Flags { // 1300 - screenshots // TODO(b/254512719): Tracking Bug - @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true) + @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, teamfood = true) // TODO(b/254513155): Tracking Bug @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301) @@ -327,7 +333,7 @@ object Flags { val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true) // 1700 - clipboard - @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true) + @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, teamfood = true) @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701) // 1800 - shade container @@ -337,7 +343,7 @@ object Flags { @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks") // 2000 - device controls - @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true) + @Keep @JvmField val USE_APP_PANELS = UnreleasedFlag(2000, teamfood = true) // 2100 - Falsing Manager @JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100) @@ -348,24 +354,48 @@ object Flags { // | . . . . . . . . . . . . . . . . . . . | @JvmStatic fun collectFlags(): Map<Int, Flag<*>> { - return flagFields - .map { field -> - // field[null] returns the current value of the field. - // See java.lang.Field#get - val flag = field[null] as Flag<*> - flag.id to flag - } - .toMap() + return flagFields.mapKeys { field -> field.value.id } } // | . . . . . . . . . . . . . . . . . . . | @JvmStatic - val flagFields: List<Field> - get() { - return Flags::class.java.fields.filter { f -> - Flag::class.java.isAssignableFrom(f.type) + val flagFields: Map<String, Flag<*>> + get() = collectFlagsInClass(Flags) + + @VisibleForTesting + fun collectFlagsInClass(instance: Any): Map<String, Flag<*>> { + val cls = instance::class + val javaPropNames = cls.java.fields.map { it.name } + val props = cls.declaredMembers + val staticProps = cls.staticProperties + val staticPropNames = staticProps.map { it.name } + return props + .mapNotNull { property -> + if ((property.returnType.classifier as KClass<*>).isSubclassOf(Flag::class)) { + // Fields with @JvmStatic should be accessed via java mechanisms + if (javaPropNames.contains(property.name)) { + property.name to cls.java.getField(property.name)[null] as Flag<*> + // Fields with @Keep but not @JvmField. Don't do this. + } else if (staticPropNames.contains(property.name)) { + // The below code causes access violation exceptions. I don't know why. + // property.name to (property.call() as Flag<*>) + // property.name to (staticProps.find { it.name == property.name }!! + // .getter.call() as Flag<*>) + throw java.lang.RuntimeException( + "The {$property.name} flag needs @JvmField" + ) + // Everything else. Skip the `get` prefixed fields that kotlin adds. + } else if (property.name.subSequence(0, 3) != "get") { + property.name to (property.call(instance) as Flag<*>) + } else { + null + } + } else { + null + } } - } + .toMap() + } // | | // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/ } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index ef87fb49752d..dc9dcc295e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -29,6 +29,7 @@ import android.widget.FrameLayout; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.util.LargeScreenUtils; import java.io.PrintWriter; @@ -52,6 +53,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private boolean mQsDisabled; private int mContentHorizontalPadding = -1; private boolean mClippingEnabled; + private boolean mUseCombinedHeaders; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -66,6 +68,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } + void setUseCombinedHeaders(boolean useCombinedHeaders) { + mUseCombinedHeaders = useCombinedHeaders; + } + @Override public boolean hasOverlappingRendering() { return false; @@ -143,9 +149,15 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { void updateResources(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController) { + int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); + if (mUseCombinedHeaders + && !LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) { + topPadding = mContext.getResources() + .getDimensionPixelSize(R.dimen.large_screen_shade_header_height); + } mQSPanelContainer.setPaddingRelative( mQSPanelContainer.getPaddingStart(), - QSUtils.getQsHeaderSystemIconsAreaHeight(mContext), + topPadding, mQSPanelContainer.getPaddingEnd(), mQSPanelContainer.getPaddingBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index dea7bb5abd71..28b4c8228d38 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -22,6 +22,8 @@ import android.content.res.Configuration; import android.view.MotionEvent; import android.view.View; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -37,7 +39,6 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { private final ConfigurationController mConfigurationController; private final FalsingManager mFalsingManager; private final NonInterceptingScrollView mQSPanelContainer; - private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override @@ -65,13 +66,15 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController, ConfigurationController configurationController, - FalsingManager falsingManager) { + FalsingManager falsingManager, + FeatureFlags featureFlags) { super(view); mQsPanelController = qsPanelController; mQuickStatusBarHeaderController = quickStatusBarHeaderController; mConfigurationController = configurationController; mFalsingManager = falsingManager; mQSPanelContainer = mView.getQSPanelContainer(); + view.setUseCombinedHeaders(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 27d9da6c2e1e..946fe542741f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -288,8 +288,15 @@ public class QuickStatusBarHeader extends FrameLayout { } MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams(); - qqsLP.topMargin = largeScreenHeaderActive || !mUseCombinedQSHeader ? mContext.getResources() - .getDimensionPixelSize(R.dimen.qqs_layout_margin_top) : qsOffsetHeight; + if (largeScreenHeaderActive) { + qqsLP.topMargin = mContext.getResources() + .getDimensionPixelSize(R.dimen.qqs_layout_margin_top); + } else if (!mUseCombinedQSHeader) { + qqsLP.topMargin = qsOffsetHeight; + } else { + qqsLP.topMargin = mContext.getResources() + .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height); + } mHeaderQsPanel.setLayoutParams(qqsLP); updateBatteryMode(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 6b540aa9f392..63d0d169e874 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -246,6 +246,8 @@ class LargeScreenShadeHeaderController @Inject constructor( qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) if (header is MotionLayout) { loadConstraints() + header.minHeight = resources + .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) lastInsets?.let { updateConstraintsForInsets(header, it) } } updateResources() diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 350f49bcf37d..cc6c70375726 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1152,8 +1152,15 @@ public final class NotificationPanelViewController { mLargeScreenShadeHeaderHeight = mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height); - mQuickQsHeaderHeight = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : - SystemBarUtils.getQuickQsOffsetHeight(mView.getContext()); + // TODO: When the flag is eventually removed, it means that we have a single view that is + // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually + // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class + // that controls the view as the offset needs to be the same regardless. + if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) { + mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight; + } else { + mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext()); + } int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top); mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 50528bdac992..f2b86035200b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -51,7 +51,6 @@ import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; -import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; @@ -80,6 +79,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; +import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; @@ -138,6 +138,7 @@ public class KeyguardIndicationController { private final KeyguardStateController mKeyguardStateController; protected final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final AuthController mAuthController; private ViewGroup mIndicationArea; private KeyguardIndicationTextView mTopIndicationView; private KeyguardIndicationTextView mLockScreenIndicationView; @@ -202,6 +203,7 @@ public class KeyguardIndicationController { } } }; + private boolean mFaceLockedOutThisAuthSession; /** * Creates a new KeyguardIndicationController and registers callbacks. @@ -222,6 +224,7 @@ public class KeyguardIndicationController { @Main DelayableExecutor executor, @Background DelayableExecutor bgExecutor, FalsingManager falsingManager, + AuthController authController, LockPatternUtils lockPatternUtils, ScreenLifecycle screenLifecycle, KeyguardBypassController keyguardBypassController, @@ -241,6 +244,7 @@ public class KeyguardIndicationController { mExecutor = executor; mBackgroundExecutor = bgExecutor; mLockPatternUtils = lockPatternUtils; + mAuthController = authController; mFalsingManager = falsingManager; mKeyguardBypassController = keyguardBypassController; mAccessibilityManager = accessibilityManager; @@ -756,7 +760,7 @@ public class KeyguardIndicationController { * logic. */ private void showBiometricMessage(CharSequence biometricMessage, - CharSequence biometricMessageFollowUp) { + @Nullable CharSequence biometricMessageFollowUp) { if (TextUtils.equals(biometricMessage, mBiometricMessage)) { return; } @@ -1064,9 +1068,7 @@ public class KeyguardIndicationController { && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; final boolean faceAuthFailed = biometricSourceType == FACE && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed - final boolean isUnlockWithFingerprintPossible = - mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser()); + final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint(); final boolean isCoExFaceAcquisitionMessage = faceAuthSoftError && isUnlockWithFingerprintPossible; if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) { @@ -1109,6 +1111,13 @@ public class KeyguardIndicationController { } @Override + public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) { + mFaceLockedOutThisAuthSession = false; + } + } + + @Override public void onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType) { if (biometricSourceType == FACE) { @@ -1119,46 +1128,18 @@ public class KeyguardIndicationController { } private void onFaceAuthError(int msgId, String errString) { - CharSequence deferredFaceMessage = null; - if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { - deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); - debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage); - } + CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); mFaceAcquiredMessageDeferral.reset(); if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) { debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString); - } else if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { - // Co-ex: show deferred message OR nothing - if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser())) { - // if we're on the lock screen (bouncer isn't showing), show the deferred msg - if (deferredFaceMessage != null - && !mStatusBarKeyguardViewManager.isBouncerShowing()) { - showBiometricMessage( - deferredFaceMessage, - mContext.getString(R.string.keyguard_suggest_fingerprint) - ); - return; - } - - // otherwise, don't show any message - debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); - return; - } - - // Face-only: The face timeout message is not very actionable, let's ask the user to - // manually retry. - if (deferredFaceMessage != null) { - showBiometricMessage( - deferredFaceMessage, - mContext.getString(R.string.keyguard_unlock) - ); - } else { - // suggest swiping up to unlock (try face auth again or swipe up to bouncer) - showActionToUnlock(); - } + return; + } + if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { + handleFaceAuthTimeoutError(deferredFaceMessage); + } else if (isLockoutError(msgId)) { + handleFaceLockoutError(errString); } else { - handleGenericBiometricError(errString); + showErrorMessageNowOrLater(errString, null); } } @@ -1167,7 +1148,7 @@ public class KeyguardIndicationController { debugLog("suppressingFingerprintError msgId=" + msgId + " errString= " + errString); } else { - handleGenericBiometricError(errString); + showErrorMessageNowOrLater(errString, null); } } @@ -1178,7 +1159,7 @@ public class KeyguardIndicationController { // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the // check of whether non-strong biometric is allowed return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) - && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) + && !isLockoutError(msgId)) || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED); @@ -1267,17 +1248,76 @@ public class KeyguardIndicationController { } } + private void handleFaceLockoutError(String errString) { + int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint + : R.string.keyguard_unlock; + String followupMessage = mContext.getString(followupMsgId); + // Lockout error can happen multiple times in a session because we trigger face auth + // even when it is locked out so that the user is aware that face unlock would have + // triggered but didn't because it is locked out. + + // On first lockout we show the error message from FaceManager, which tells the user they + // had too many unsuccessful attempts. + if (!mFaceLockedOutThisAuthSession) { + mFaceLockedOutThisAuthSession = true; + showErrorMessageNowOrLater(errString, followupMessage); + } else if (!mAuthController.isUdfpsFingerDown()) { + // On subsequent lockouts, we show a more generic locked out message. + showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable), + followupMessage); + } + } + + private static boolean isLockoutError(int msgId) { + return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT + || msgId == FaceManager.FACE_ERROR_LOCKOUT; + } + + private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) { + debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage); + if (canUnlockWithFingerprint()) { + // Co-ex: show deferred message OR nothing + // if we're on the lock screen (bouncer isn't showing), show the deferred msg + if (deferredFaceMessage != null + && !mStatusBarKeyguardViewManager.isBouncerShowing()) { + showBiometricMessage( + deferredFaceMessage, + mContext.getString(R.string.keyguard_suggest_fingerprint) + ); + } else { + // otherwise, don't show any message + debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); + } + } else if (deferredFaceMessage != null) { + // Face-only: The face timeout message is not very actionable, let's ask the + // user to manually retry. + showBiometricMessage( + deferredFaceMessage, + mContext.getString(R.string.keyguard_unlock) + ); + } else { + // Face-only + // suggest swiping up to unlock (try face auth again or swipe up to bouncer) + showActionToUnlock(); + } + } + + private boolean canUnlockWithFingerprint() { + return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + KeyguardUpdateMonitor.getCurrentUser()); + } + private void debugLog(String logMsg) { if (DEBUG) { Log.d(TAG, logMsg); } } - private void handleGenericBiometricError(String errString) { + private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { - showBiometricMessage(errString); + showBiometricMessage(errString, followUpMsg); } else { mBiometricErrorMessageToShowOnScreenOn = errString; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 408293cffd99..815b86e0adb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -805,7 +805,7 @@ public class NotificationShelf extends ActivatableNotificationView implements iconState.hidden = isAppearing || (view instanceof ExpandableNotificationRow && ((ExpandableNotificationRow) view).isLowPriority() - && mShelfIcons.hasMaxNumDot()) + && mShelfIcons.areIconsOverflowing()) || (transitionAmount == 0.0f && !iconState.isAnimating(icon)) || row.isAboveShelf() || row.showingPulsing() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index c189acec2930..4ee2de11abdf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -141,7 +141,6 @@ public class NotificationIconContainer extends ViewGroup { /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */ public static final int MAX_ICONS_ON_LOCKSCREEN = 3; public static final int MAX_STATIC_ICONS = 4; - private static final int MAX_DOTS = 1; private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); @@ -166,8 +165,7 @@ public class NotificationIconContainer extends ViewGroup { private IconState mLastVisibleIconState; private IconState mFirstVisibleIconState; private float mVisualOverflowStart; - // Keep track of overflow in range [0, 3] - private int mNumDots; + private boolean mIsShowingOverflowDot; private StatusBarIconView mIsolatedIcon; private Rect mIsolatedIconLocation; private int[] mAbsolutePosition = new int[2]; @@ -387,8 +385,8 @@ public class NotificationIconContainer extends ViewGroup { } } - public boolean hasMaxNumDot() { - return mNumDots >= MAX_DOTS; + public boolean areIconsOverflowing() { + return mIsShowingOverflowDot; } private boolean areAnimationsEnabled(StatusBarIconView icon) { @@ -494,7 +492,7 @@ public class NotificationIconContainer extends ViewGroup { : 1f; translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; } - mNumDots = 0; + mIsShowingOverflowDot = false; if (firstOverflowIndex != -1) { translationX = mVisualOverflowStart; for (int i = firstOverflowIndex; i < childCount; i++) { @@ -502,15 +500,14 @@ public class NotificationIconContainer extends ViewGroup { IconState iconState = mIconStates.get(view); int dotWidth = mStaticDotDiameter + mDotPadding; iconState.setXTranslation(translationX); - if (mNumDots < MAX_DOTS) { - if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) { + if (!mIsShowingOverflowDot) { + if (iconState.iconAppearAmount < 0.8f) { iconState.visibleState = StatusBarIconView.STATE_ICON; } else { iconState.visibleState = StatusBarIconView.STATE_DOT; - mNumDots++; + mIsShowingOverflowDot = true; } - translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth) - * iconState.iconAppearAmount; + translationX += dotWidth * iconState.iconAppearAmount; mLastVisibleIconState = iconState; } else { iconState.visibleState = StatusBarIconView.STATE_HIDDEN; @@ -618,10 +615,6 @@ public class NotificationIconContainer extends ViewGroup { return Math.min(getWidth(), translation); } - private float getMaxOverflowStart() { - return getLayoutEnd() - mIconSize; - } - public void setChangingViewPositions(boolean changingViewPositions) { mChangingViewPositions = changingViewPositions; } @@ -645,25 +638,6 @@ public class NotificationIconContainer extends ViewGroup { mSpeedBumpIndex = speedBumpIndex; } - public boolean hasOverflow() { - return mNumDots > 0; - } - - // Give some extra room for btw notifications if we can - public int getNoOverflowExtraPadding() { - if (mNumDots != 0) { - return 0; - } - - int collapsedPadding = mIconSize; - - if (collapsedPadding + getFinalTranslationX() > getWidth()) { - collapsedPadding = getWidth() - getFinalTranslationX(); - } - - return collapsedPadding; - } - public int getIconSize() { return mIconSize; } diff --git a/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt new file mode 100644 index 000000000000..12a0c03a2ad7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.pm.ActivityInfo +import android.content.res.Resources +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import android.graphics.drawable.InsetDrawable + +/** + * [DrawableWrapper] to use in the progress of brightness slider. + * + * This drawable is used to change the bounds of the enclosed drawable depending on the level to + * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the + * edges is maintained. + * + * Meant to be used with a rounded ends background, it will also prevent deformation when the slider + * is meant to be smaller than the rounded corner. The background should have rounded corners that + * are half of the height. + * + * This class also assumes that a "thumb" icon exists within the end's edge of the progress + * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb + * icon which puts the icon directly underneath the user's finger. + */ +class BrightnessProgressDrawable @JvmOverloads constructor(drawable: Drawable? = null) : + InsetDrawable(drawable, 0) { + + companion object { + private const val MAX_LEVEL = 10000 // Taken from Drawable + } + + override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean { + onLevelChange(level) + return super.onLayoutDirectionChanged(layoutDirection) + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + onLevelChange(level) + } + + override fun onLevelChange(level: Int): Boolean { + val db = drawable?.bounds!! + + // The thumb offset shifts the sun icon directly under the user's thumb + val thumbOffset = bounds.height() / 2 + val width = bounds.width() * level / MAX_LEVEL + thumbOffset + + // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width + drawable?.setBounds( + bounds.left, + db.top, + width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()), + db.bottom + ) + return super.onLevelChange(level) + } + + override fun getConstantState(): ConstantState { + // This should not be null as it was created with a state in the constructor. + return RoundedCornerState(super.getConstantState()!!) + } + + override fun getChangingConfigurations(): Int { + return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY + } + + override fun canApplyTheme(): Boolean { + return (drawable?.canApplyTheme() ?: false) || super.canApplyTheme() + } + + private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() { + override fun newDrawable(): Drawable { + return newDrawable(null, null) + } + + override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable { + val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper + return BrightnessProgressDrawable(wrapper.drawable) + } + + override fun getChangingConfigurations(): Int { + return wrappedState.changingConfigurations + } + + override fun canApplyTheme(): Boolean { + return true + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt index 99eb03b44276..1059d6c61287 100644 --- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt @@ -33,11 +33,6 @@ import android.graphics.drawable.InsetDrawable * Meant to be used with a rounded ends background, it will also prevent deformation when the slider * is meant to be smaller than the rounded corner. The background should have rounded corners that * are half of the height. - * - * This class also assumes that a "thumb" icon exists within the end's edge of the progress - * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb - * icon which puts the icon directly underneath the user's finger. - * */ class RoundedCornerProgressDrawable @JvmOverloads constructor( drawable: Drawable? = null @@ -59,16 +54,9 @@ class RoundedCornerProgressDrawable @JvmOverloads constructor( override fun onLevelChange(level: Int): Boolean { val db = drawable?.bounds!! - - // The thumb offset shifts the sun icon directly under the user's thumb - val thumbOffset = bounds.height() / 2 - val width = bounds.width() * level / MAX_LEVEL + thumbOffset - // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width - drawable?.setBounds( - bounds.left, db.top, - width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()), db.bottom - ) + val width = bounds.height() + (bounds.width() - bounds.height()) * level / MAX_LEVEL + drawable?.setBounds(bounds.left, db.top, bounds.left + width, db.bottom) return super.onLevelChange(level) } @@ -103,4 +91,4 @@ class RoundedCornerProgressDrawable @JvmOverloads constructor( return true } } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index a8284d29197d..9c14ee610b25 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1621,7 +1621,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse() + public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue() throws RemoteException { // Preconditions for face auth to run keyguardNotGoingAway(); @@ -1638,7 +1638,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { faceAuthLockedOut(); mTestableLooper.processAllMessages(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); + // This is needed beccause we want to show face locked out error message whenever face auth + // is supposed to run. + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java deleted file mode 100644 index 250cc48fece3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.flags; - -import static com.google.common.truth.Truth.assertWithMessage; - -import android.util.Pair; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@SmallTest -public class FlagsTest extends SysuiTestCase { - - @Test - public void testDuplicateFlagIdCheckWorks() { - List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class); - Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags); - - assertWithMessage(generateAssertionMessage(duplicates)) - .that(duplicates.size()).isEqualTo(2); - } - - @Test - public void testNoDuplicateFlagIds() { - List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class); - Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags); - - assertWithMessage(generateAssertionMessage(duplicates)) - .that(duplicates.size()).isEqualTo(0); - } - - private String generateAssertionMessage(Map<Integer, List<String>> duplicates) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("Duplicate flag keys found: {"); - for (int id : duplicates.keySet()) { - stringBuilder - .append(" ") - .append(id) - .append(": [") - .append(String.join(", ", duplicates.get(id))) - .append("]"); - } - stringBuilder.append(" }"); - - return stringBuilder.toString(); - } - - private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) { - List<Pair<String, Flag<?>>> flags = new ArrayList<>(); - - Field[] fields = clz.getFields(); - - for (Field field : fields) { - Class<?> t = field.getType(); - if (Flag.class.isAssignableFrom(t)) { - try { - flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null))); - } catch (IllegalAccessException e) { - // no-op - } - } - } - - return flags; - } - - private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) { - Map<Integer, List<String>> grouping = new HashMap<>(); - - for (Pair<String, Flag<?>> flag : flags) { - grouping.putIfAbsent(flag.second.getId(), new ArrayList<>()); - grouping.get(flag.second.getId()).add(flag.first); - } - - Map<Integer, List<String>> result = new HashMap<>(); - for (Integer id : grouping.keySet()) { - if (grouping.get(id).size() > 1) { - result.put(id, grouping.get(id)); - } - } - - return result; - } - - private static class DuplicateFlagContainer { - public static final BooleanFlag A_FLAG = new UnreleasedFlag(0); - public static final BooleanFlag B_FLAG = new UnreleasedFlag(0); - public static final StringFlag C_FLAG = new StringFlag(0); - - public static final BooleanFlag D_FLAG = new UnreleasedFlag(1); - - public static final DoubleFlag E_FLAG = new DoubleFlag(3); - public static final DoubleFlag F_FLAG = new DoubleFlag(3); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt new file mode 100644 index 000000000000..2b556f112e5f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.flags + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth +import java.lang.StringBuilder +import java.util.ArrayList +import java.util.HashMap +import org.junit.Test + +@SmallTest +class FlagsTest : SysuiTestCase() { + @Test + fun testDuplicateFlagIdCheckWorks() { + val flags = Flags.collectFlagsInClass(DuplicateFlagContainer) + val duplicates = groupDuplicateFlags(flags) + Truth.assertWithMessage(generateAssertionMessage(duplicates)) + .that(duplicates.size) + .isEqualTo(2) + } + + @Test + fun testNoDuplicateFlagIds() { + val flags = Flags.collectFlagsInClass(Flags) + val duplicates = groupDuplicateFlags(flags) + Truth.assertWithMessage(generateAssertionMessage(duplicates)) + .that(duplicates.size) + .isEqualTo(0) + } + + private fun generateAssertionMessage(duplicates: Map<Int, List<String>>): String { + val stringBuilder = StringBuilder() + stringBuilder.append("Duplicate flag keys found: {") + for (id in duplicates.keys) { + stringBuilder + .append(" ") + .append(id) + .append(": [") + .append(java.lang.String.join(", ", duplicates[id])) + .append("]") + } + stringBuilder.append(" }") + return stringBuilder.toString() + } + + private fun groupDuplicateFlags(flags: Map<String, Flag<*>>): Map<Int, List<String>> { + val grouping: MutableMap<Int, MutableList<String>> = HashMap() + for (flag in flags) { + grouping.putIfAbsent(flag.value.id, ArrayList()) + grouping[flag.value.id]!!.add(flag.key) + } + val result: MutableMap<Int, List<String>> = HashMap() + for (id in grouping.keys) { + if (grouping[id]!!.size > 1) { + result[id] = grouping[id]!! + } + } + return result + } + + private object DuplicateFlagContainer { + val A_FLAG: BooleanFlag = UnreleasedFlag(0) + val B_FLAG: BooleanFlag = UnreleasedFlag(0) + val C_FLAG = StringFlag(0) + val D_FLAG: BooleanFlag = UnreleasedFlag(1) + val E_FLAG = DoubleFlag(3) + val F_FLAG = DoubleFlag(3) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 9a13e93ebcfc..9b17cc296081 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; @@ -54,6 +55,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.Instrumentation; @@ -88,6 +90,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; @@ -173,6 +176,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private FaceHelpMessageDeferral mFaceHelpMessageDeferral; @Mock private ScreenLifecycle mScreenLifecycle; + @Mock + private AuthController mAuthController; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @Captor @@ -263,8 +268,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mWakeLockBuilder, mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, - mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils, - mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, + mUserManager, mExecutor, mExecutor, mFalsingManager, + mAuthController, mLockPatternUtils, mScreenLifecycle, + mKeyguardBypassController, mAccessibilityManager, mFaceHelpMessageDeferral); mController.init(); mController.setIndicationArea(mIndicationArea); @@ -1371,6 +1377,110 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } + @Test + public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() { + createController(); + onFaceLockoutError("first lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout"); + } + + @Test + public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() { + createController(); + fingerprintUnlockIsPossible(); + onFaceLockoutError("first lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_suggest_fingerprint)); + } + + @Test + public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() { + createController(); + fingerprintUnlockIsNotPossible(); + onFaceLockoutError("first lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_unlock)); + } + + @Test + public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() { + createController(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + + onFaceLockoutError("second lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, + mContext.getString(R.string.keyguard_face_unlock_unavailable)); + } + + @Test + public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() { + createController(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + + when(mAuthController.isUdfpsFingerDown()).thenReturn(true); + onFaceLockoutError("second lockout"); + + verifyNoMoreInteractions(mRotateTextViewController); + } + + @Test + public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() { + createController(); + fingerprintUnlockIsPossible(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + + onFaceLockoutError("second lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_suggest_fingerprint)); + } + + @Test + public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() { + createController(); + fingerprintUnlockIsNotPossible(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + + onFaceLockoutError("second lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_unlock)); + } + + @Test + public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() { + createController(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false); + mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE); + + onFaceLockoutError("second lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout"); + } + + @Test + public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() { + createController(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true); + mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE); + + onFaceLockoutError("second lockout"); + + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, + mContext.getString(R.string.keyguard_face_unlock_unavailable)); + } private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); @@ -1419,4 +1529,33 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { anyObject(), anyBoolean()); } } + + private void verifyIndicationShown(int indicationType, String message) { + verify(mRotateTextViewController) + .updateIndication(eq(indicationType), + mKeyguardIndicationCaptor.capture(), + eq(true)); + assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString()) + .isEqualTo(message); + } + + private void fingerprintUnlockIsNotPossible() { + setupFingerprintUnlockPossible(false); + } + + private void fingerprintUnlockIsPossible() { + setupFingerprintUnlockPossible(true); + } + + private void setupFingerprintUnlockPossible(boolean possible) { + when(mKeyguardUpdateMonitor + .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser())) + .thenReturn(possible); + } + + private void onFaceLockoutError(String errMsg) { + mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT, + errMsg, + BiometricSourceType.FACE); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt index 086e5df50f3f..b80b825d87dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -92,7 +92,7 @@ class NotificationIconContainerTest : SysuiTestCase() { iconContainer.calculateIconXTranslations() assertEquals(10f, iconState.xTranslation) - assertFalse(iconContainer.hasOverflow()) + assertFalse(iconContainer.areIconsOverflowing()) } @Test @@ -121,7 +121,7 @@ class NotificationIconContainerTest : SysuiTestCase() { assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation) assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation) - assertFalse(iconContainer.hasOverflow()) + assertFalse(iconContainer.areIconsOverflowing()) } @Test @@ -150,7 +150,7 @@ class NotificationIconContainerTest : SysuiTestCase() { assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation) assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation) assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation) - assertTrue(iconContainer.hasOverflow()) + assertTrue(iconContainer.areIconsOverflowing()) } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index c132537eeb71..a35427f5ec3a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -27,9 +27,8 @@ class FakeFeatureFlags : FeatureFlags { private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>() init { - Flags.flagFields.forEach { field -> - val flag: Flag<*> = field.get(null) as Flag<*> - knownFlagNames[flag.id] = field.name + Flags.flagFields.forEach { entry: Map.Entry<String, Flag<*>> -> + knownFlagNames[entry.value.id] = entry.key } } diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java index 3f1d1feec282..ae50b2358139 100644 --- a/services/core/java/com/android/server/VpnManagerService.java +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -186,6 +186,10 @@ public class VpnManagerService extends IVpnManager.Stub { synchronized (mVpns) { for (int i = 0; i < mVpns.size(); i++) { pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage()); + pw.increaseIndent(); + mVpns.valueAt(i).dump(pw); + pw.decreaseIndent(); + pw.println(); } pw.decreaseIndent(); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 6a000d9f96cf..0741d46b6f29 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -127,6 +127,8 @@ import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyPermission; import android.text.TextUtils; import android.util.ArraySet; +import android.util.IndentingPrintWriter; +import android.util.LocalLog; import android.util.Log; import android.util.Range; @@ -296,6 +298,10 @@ public class Vpn { return mVpnProfileStore; } + private static final int MAX_EVENTS_LOGS = 20; + private final LocalLog mUnderlyNetworkChanges = new LocalLog(MAX_EVENTS_LOGS); + private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS); + /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This * only applies to {@link VpnService} connections. @@ -841,6 +847,9 @@ public class Vpn { int errorCode, @NonNull final String packageName, @Nullable final String sessionKey, @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork, @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) { + mVpnManagerEvents.log("Event class=" + getVpnManagerEventClassName(errorClass) + + ", err=" + getVpnManagerEventErrorName(errorCode) + " for " + packageName + + " on session " + sessionKey); final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode, packageName, sessionKey, profileState, underlyingNetwork, nc, lp); return sendEventToVpnManagerApp(intent, packageName); @@ -1572,6 +1581,7 @@ public class Vpn { ? Arrays.asList(mConfig.underlyingNetworks) : null); mNetworkCapabilities = capsBuilder.build(); + logUnderlyNetworkChanges(mNetworkCapabilities.getUnderlyingNetworks()); mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */, mNetworkCapabilities, lp, new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(), @@ -1599,6 +1609,11 @@ public class Vpn { } } + private void logUnderlyNetworkChanges(List<Network> networks) { + mUnderlyNetworkChanges.log("Switch to " + + ((networks != null) ? TextUtils.join(", ", networks) : "null")); + } + private void agentDisconnect(NetworkAgent networkAgent) { if (networkAgent != null) { networkAgent.unregister(); @@ -4372,6 +4387,7 @@ public class Vpn { // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from // ConnectivityServiceTest. if (SdkLevel.isAtLeastT()) { + mVpnManagerEvents.log(packageName + " stopped"); sendEventToVpnManagerApp(intent, packageName); } } @@ -4539,8 +4555,10 @@ public class Vpn { /** Proxy to allow different testing setups */ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when // NetworkAgent#setUnderlyingNetworks can be un-finalized. - private static void doSetUnderlyingNetworks( + private void doSetUnderlyingNetworks( @NonNull NetworkAgent agent, @NonNull List<Network> networks) { + logUnderlyNetworkChanges(networks); + if (agent instanceof VpnNetworkAgentWrapper) { ((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks); } else { @@ -4659,4 +4677,57 @@ public class Vpn { static Range<Integer> createUidRangeForUser(int userId) { return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); } + + private String getVpnManagerEventClassName(int code) { + switch (code) { + case VpnManager.ERROR_CLASS_NOT_RECOVERABLE: + return "ERROR_CLASS_NOT_RECOVERABLE"; + case VpnManager.ERROR_CLASS_RECOVERABLE: + return "ERROR_CLASS_RECOVERABLE"; + default: + return "UNKNOWN_CLASS"; + } + } + + private String getVpnManagerEventErrorName(int code) { + switch (code) { + case VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST: + return "ERROR_CODE_NETWORK_UNKNOWN_HOST"; + case VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT: + return "ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT"; + case VpnManager.ERROR_CODE_NETWORK_IO: + return "ERROR_CODE_NETWORK_IO"; + case VpnManager.ERROR_CODE_NETWORK_LOST: + return "ERROR_CODE_NETWORK_LOST"; + default: + return "UNKNOWN_ERROR"; + } + } + + /** Dumps VPN state. */ + public void dump(IndentingPrintWriter pw) { + synchronized (Vpn.this) { + pw.println("Active package name: " + mPackage); + pw.println("Active vpn type: " + getActiveVpnType()); + pw.println("NetworkCapabilities: " + mNetworkCapabilities); + if (isIkev2VpnRunner()) { + final IkeV2VpnRunner runner = ((IkeV2VpnRunner) mVpnRunner); + pw.println("Token: " + runner.mSessionKey); + pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled")); + if (mDataStallSuspected) pw.println("Data stall suspected"); + if (runner.mScheduledHandleDataStallFuture != null) { + pw.println("Reset session scheduled"); + } + } + pw.println("mUnderlyNetworkChanges (most recent first):"); + pw.increaseIndent(); + mUnderlyNetworkChanges.reverseDump(pw); + pw.decreaseIndent(); + + pw.println("mVpnManagerEvent (most recent first):"); + pw.increaseIndent(); + mVpnManagerEvents.reverseDump(pw); + pw.decreaseIndent(); + } + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b8ff6ed93666..7806ece9e65f 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2598,7 +2598,7 @@ public final class DisplayManagerService extends SystemService { // initPowerManagement has not yet been called. return; } - if (mBrightnessTracker == null) { + if (mBrightnessTracker == null && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) { mBrightnessTracker = new BrightnessTracker(mContext, null); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 9485bcce627b..89beeed88f3b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -913,7 +913,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Initialize all of the brightness tracking state final float brightness = convertToNits(mPowerState.getScreenBrightness()); - if (brightness >= PowerManager.BRIGHTNESS_MIN) { + if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) { mBrightnessTracker.start(brightness); } mBrightnessSettingListener = brightnessValue -> { @@ -1045,7 +1045,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } loadAmbientLightSensor(); - if (mBrightnessTracker != null) { + // BrightnessTracker should only use one light sensor, we want to use the light sensor + // from the default display and not e.g. temporary displays when switching layouts. + if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) { mBrightnessTracker.setLightSensor(mLightSensor); } @@ -2453,7 +2455,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f - && mAutomaticBrightnessController != null) { + && mAutomaticBrightnessController != null && mBrightnessTracker != null) { // We only want to track changes on devices that can actually map the display backlight // values into a physical brightness unit since the value provided by the API is in // nits and not using the arbitrary backlight units. |